[Accepted] SE-0390: Noncopyable structs and enums

Hello, Swift community.

The second review of SE-0390 ran from March 21st through April 4th. The second review was narrowly focused on the revisions made after the first review:

  • The spelling of the "negative constraint" ~Copyable.
  • The spelling of the discard self operator and its constraints.

There was a lot of discussion about how to spell the negative constraint; there was a lot of discussion within the language workgroup as well. Ultimately, we felt that none of the offered alternatives were clearly better, so we went with what was in the proposal text. The spelling is ~Copyable.

There are three minor changes to the proposal that the workgroup agreed to make doing our discussion:

  • At the very end of the review, it was noted by one of the proposal authors that there would be no easy way to suppress an unused variable warning with _ = x or let _ = x when x is a noncopyable binding, if those assignments were consuming. The workgroup agreed to adopt the rule that assignment to _ will be interpreted as a borrowing, rather than consuming operation. If you want to end the lifetime, that can be written with _ = consume x.

  • Noncopyable enums cannot have a deinit.

  • When switching over a noncopyable type, you must switch consume self.

The latter two might be relaxed via future proposals; they are pragmatic considerations more than fundamental design requirements.

SE-0390 has been accepted with the above modifications. The proposal text will be updated to include them.

Thanks to both the community and the proposal authors for their patience.

Steve Canon
Review Manager

26 Likes

Thanks Steve! I have amended the proposal to follow the Language Steering Group's recommendations:

I have also subset out a couple of aspects of the original proposal to correspond to what we've been able to implement so far:

9 Likes

Whoa, just seeing this in a WWDC video and I was so confused why a noncopyable type was conforming to a protocol called "copyable". Is ~ really a well-known symbol for negation? Doesn't it mean approximate??

4 Likes

It’s the bitwise NOT operator in Swift (and most C-derived languages), so yes, that’s a common reading.

1 Like

Anyone able to use deinit with noncopyable types? I am getting compiler crash with latest Xcode beta for the below code:

public struct SomeStruct: ~Copyable {
    public let val: Int

    public init(val: Int) {
        self.val = val
    }

    deinit {
        debugPrint("deinit called")
    }
}

let val = SomeStruct(val: 6)
print(val.val)
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
0.	Program arguments: "/Applications/Xcode 15.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-frontend" -frontend -c -primary-file /Users/user/Documents/projects/pocs/OwenerShip/Sources/app/main.swift -emit-dependencies-path /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/Objects-normal/arm64/main.d -emit-const-values-path /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/Objects-normal/arm64/main.swiftconstvalues -emit-reference-dependencies-path /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/Objects-normal/arm64/main.swiftdeps -serialize-diagnostics-path /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/Objects-normal/arm64/main.dia -target arm64-apple-macos10.13 -Xllvm -aarch64-use-tbi -enable-objc-interop -sdk "/Applications/Xcode 15.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk" -I /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Products/Debug -I "/Applications/Xcode 15.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib" -F /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Products/Debug/PackageFrameworks -F /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Products/Debug -F "/Applications/Xcode 15.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks" -no-color-diagnostics -enable-testing -g -module-cache-path /Users/user/Library/Developer/Xcode/DerivedData/ModuleCache.noindex -swift-version 5 -enforce-exclusivity=checked -Onone -D SWIFT_PACKAGE -D DEBUG -D Xcode -serialize-debugging-options -const-gather-protocols-file /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/Objects-normal/arm64/app_const_extract_protocols.json -empty-abi-descriptor -plugin-path "/Applications/Xcode 15.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/plugins" -plugin-path "/Applications/Xcode 15.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/local/lib/swift/host/plugins" -validate-clang-modules-once -clang-build-session-file /Users/user/Library/Developer/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation -Xcc -working-directory -Xcc /Users/user/Documents/projects/pocs/OwenerShip -resource-dir "/Applications/Xcode 15.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift" -enable-anonymous-context-mangled-names -Xcc -ivfsstatcache -Xcc /Users/user/Library/Developer/Xcode/DerivedData/SDKStatCaches.noindex/macosx14.0-23A5257p-a69c470919f1a9e72aba1cb80e516da1.sdkstatcache -Xcc -I/Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Products/Debug/include -Xcc -I/Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/DerivedSources-normal/arm64 -Xcc -I/Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/DerivedSources/arm64 -Xcc -I/Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/DerivedSources -Xcc -DSWIFT_PACKAGE -Xcc -DDEBUG=1 -module-name app -frontend-parseable-output -disable-clang-spi -target-sdk-version 14.0 -target-sdk-name macosx14.0 -o /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Build/Intermediates.noindex/OwenerShip.build/Debug/app.build/Objects-normal/arm64/main.o -index-unit-output-path /OwenerShip.build/Debug/app.build/Objects-normal/arm64/main.o -index-store-path /Users/user/Library/Developer/Xcode/DerivedData/OwenerShip-gfxsgnvleqgcsgbaudpirbusvmrq/Index.noindex/DataStore -index-system-modules
1.	Apple Swift version 5.9 (swiftlang-5.9.0.114.6 clang-1500.0.27.1)
2.	Compiling with the current language version
3.	While evaluating request IRGenRequest(IR Generation for file "/Users/user/Documents/projects/pocs/OwenerShip/Sources/app/main.swift")
4.	While emitting IR SIL function "@$s3app8someFuncyyF".
 for 'someFunc()' (at /Users/user/Documents/projects/pocs/OwenerShip/Sources/app/main.swift:4:1)
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0  swift-frontend           0x00000001098f391c llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1  swift-frontend           0x00000001098f2ae0 llvm::sys::RunSignalHandlers() + 112
2  swift-frontend           0x00000001098f3f1c SignalHandler(int) + 352
3  libsystem_platform.dylib 0x00000001a34cea24 _sigtramp + 56
4  swift-frontend           0x0000000104875cf8 tryEmitDeinitCall(swift::irgen::IRGenFunction&, swift::SILType, llvm::function_ref<void (swift::irgen::Explosion&)>, llvm::function_ref<swift::irgen::Address ()>, llvm::function_ref<void ()>) + 516
5  swift-frontend           0x0000000104852844 (anonymous namespace)::StructTypeInfoBase<(anonymous namespace)::LoadableStructTypeInfo, swift::irgen::LoadableTypeInfo, (anonymous namespace)::StructFieldInfo>::destroy(swift::irgen::IRGenFunction&, swift::irgen::Address, swift::SILType, bool) const + 140
6  swift-frontend           0x00000001048ec5d4 (anonymous namespace)::IRGenSILFunction::visitSILBasicBlock(swift::SILBasicBlock*) + 56336
7  swift-frontend           0x00000001048dce74 swift::irgen::IRGenModule::emitSILFunction(swift::SILFunction*) + 11112
8  swift-frontend           0x0000000104735638 swift::irgen::IRGenerator::emitGlobalTopLevel(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>> const&) + 2516
9  swift-frontend           0x000000010488c824 swift::IRGenRequest::evaluate(swift::Evaluator&, swift::IRGenDescriptor) const + 5400
10 swift-frontend           0x00000001048da080 swift::SimpleRequest<swift::IRGenRequest, swift::GeneratedModule (swift::IRGenDescriptor), (swift::RequestFlags)9>::evaluateRequest(swift::IRGenRequest const&, swift::Evaluator&) + 176
11 swift-frontend           0x000000010489e308 llvm::Expected<swift::IRGenRequest::OutputType> swift::Evaluator::getResultUncached<swift::IRGenRequest>(swift::IRGenRequest const&) + 1516
12 swift-frontend           0x0000000104891614 swift::performIRGeneration(swift::FileUnit*, swift::IRGenOptions const&, swift::TBDGenOptions const&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, llvm::StringRef, swift::PrimarySpecificPaths const&, llvm::StringRef, llvm::GlobalVariable**) + 264
13 swift-frontend           0x0000000104327630 generateIR(swift::IRGenOptions const&, swift::TBDGenOptions const&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, swift::PrimarySpecificPaths const&, llvm::StringRef, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, llvm::GlobalVariable*&, llvm::ArrayRef<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>>) + 156
14 swift-frontend           0x0000000104322098 performCompileStepsPostSILGen(swift::CompilerInstance&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, swift::PrimarySpecificPaths const&, int&, swift::FrontendObserver*) + 1596
15 swift-frontend           0x0000000104324d48 performCompile(swift::CompilerInstance&, int&, swift::FrontendObserver*) + 1748
16 swift-frontend           0x00000001043231e0 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 4216
17 swift-frontend           0x00000001042e8a58 swift::mainEntry(int, char const**) + 4112
18 dyld                     0x00000001a3147f28 start + 2236

deinit works on swift-5.9-DEVELOPMENT-SNAPSHOT-2023-06-05 on Ubuntu. Maybe try to download the latest toolchain from swift.org? Then just plug it in Xcode.

Btw. If you expect to see deinit called in console then you need to do following:

public struct SomeStruct: ~Copyable {
    public let val: Int

    public init(val: Int) {
        self.val = val
    }

    deinit {
        debugPrint("deinit called")
    }
}

func test() { // <--------- HERE
  let val = SomeStruct(val: 6)
  print(val.val)
}

test()

Your object lives in the global scope, if you are just testing the lifetimes then create an artificial scope, so that your object does not live forever. You can also use do this:

do {
  let val = SomeStruct(val: 6)
  print(val.val)
}

But this is more fancy, not a lot of people know about the do scoping.

Question

(This is possibly a duplicate, because it is the 1st thing you check with move-only. If that’s the case then just say so, don't waste your time on writing an answer.)

What would you expect this code to do:

struct Alice: ~Copyable {
  var age: Int

  init(age: Int) {
    print("INIT");
    self.age = age
  }

  deinit { print("DEINIT") }
}

func eatMe(_ alice: consuming Alice) {
  print(" start")
  print(" age:", alice.age)
  print(" end")
}

// In some function, you can't have it in global scope.
let alice = Alice(age: 10)
eatMe(alice)

Possible options (column-wise):

A B C
Stdout INIT
  start
DEINIT
  age: 10
  end
INIT
  start
  age: 10
DEINIT
  end
INIT
  start
  age: 10
  end
DEINIT
Why? tmp = alice.age
alice.deinit()
print(" age:", tmp)
print(" age:", alice.age) is the last proper usage of alice, so deinit should be after it. eatMe takes an ownership of alice, so the lifetime is bound to function execution.
You may expect this if you come from the other language.

A, B or C? Try to answer it without thinking about the compiler, just looking at the code.

Answer

Answer: A (swift-5.9-DEVELOPMENT-SNAPSHOT-2023-06-05).

So, basically Swift goes for the minimal lifetime. This is teknikly the most correct answer. It can even save your bacon if you are dealing with many file descriptors at the same time (though your code is still not correct if you rely on the exact position where deinit will be called).

Tbh. I thought it would be B. For most of the programmers print(" age:", alice.age) is the last usage. With current implementation it kind of looks like the argument alice was deallocated before the print call (though alice was never an argument, just their age). It is counterintuitive, or at least for me it was a bit surprising.

Other types (does not really matter)

Just for the reference this is how it works for other types (this does not really matter, consuming is an entirely different beast than those below, this is just for completeness):

Struct +
borrowing argument
Struct +
inout argument
Class
Stdout INIT
  start
  age: 10
  end
DEINIT
INIT
  start
  age: 10
  end
DEINIT
INIT
  start
  age: 10
  end
DEINIT
Why? Obviously. Same as borrowing. Conceptually Swift retains the argument, before function call.

Other remarks

Anyway, I will be adding move-only types as smart pointers in Violet - Python VM written in Swift . The whole code was already written with this in mind (2 years ago): Python object is just a struct with an pointer inside, with move-only types we can reference count + explicit retain to copy. Reference cycles are annoying to deal with, so let them be.

Other than the question above the move-only series of proposals is almost exactly what I expected (which is a good thing), so I don't have any more remarks.

Other major pain points (they will be fixed eventually, but they are still annoying):

  • No protocol conformances. Workaround:

    // Swift does not support protocol conformances on `~Copyable` types (except for Sendable).
    public typealias PyObjectMixin = Sendable
    

    Then generate the needed code via Python script (or macros or Sourcery).

  • No std lib

3 Likes

I believe that this was fixed in 6a5ef8af94e38e720ad0924b4721493a3c575b6f.

That being said, I would suggest filing a GitHub issue for any future issues you hit rather than posting in the accepted thread. Issues · apple/swift · GitHub. Thank you for trying out noncopyable types. We appreciate any issues filed. = ).

So what is happening here is that currently lifetimes of non-copyable types end at the last use. B/c Alice is a consuming parameter, it is stored into memory since consuming values are mutable values. So the reason why A is occurring now is that we are loading the value of age before we call print. Once the load has occurred, that load is the last use of the memory location.

That being said, with some time, we want C to be the correct lifetime. The specific rules will be what we called "maximized lifetimes". "Maximized lifetimes" means that:

  1. Along paths where values are consumed, the lifetime of the consumed value ends at the consuming use.
  2. Along paths where the value is not-consumed, we extend the lifetime of the value from the last non-consumed site to the end of the lexical scope, only stopping if we reach a point in the program that is reachable from a different code path where the value is consumed. The reason for this is it prevents the compiler from needing to synthesize logic that at runtime determines whether we need to clean up the value depending on the dynamic path that the program takes.

As an example of 2, consider the following Swift:

let m = MoveOnly()
if boolTest {
   print("point 1")
   let _ = consume m // end of maximal lifetime (A)
   print("point 2")
} else {
   m.doSomething
   print("point 3")
   // end of maximal lifetime (B)
}
print("point 4")
// end of scoped lifetime (C)

In words, the maximal lifetime in this case ends at (A) and (B)... while the end of scope lifetime would be at (C). This can be seen by looking at the two code paths in the function:

a. Along the if path, the lifetime must end at consume m since m is consumed by the call to the consume operator.
b. Along the else path, m is not consumed, so we begin by noticing that m.doSomething is the last use of m. Then we begin maximizing the lifetime of m to end of scope. When we hit the end of the else block, we see that the next piece of code is reachable from the if block. Since we are consumed in the if block, we stop extending and thus end the lifetime of m at the end of the else block.

If we did not do this, we would need to have the compiler synthesize runtime code so that along the if path, we do not destroy the value when we execute (C).

5 Likes

Is this something that the team plans to discuss? Rust, for example, still destroys the value at the end of the lexical scope (and does emit a flag to do it, IIRC). I don’t know if this is something they regret, though.

1 Like

The language workgroup discussed this and made the decision to go with shortened lifetimes, yeah.

4 Likes

So what is happening here is that currently lifetimes of non-copyable types end at the last use. B/c Alice is a consuming parameter, it is stored into memory since consuming values are mutable values. So the reason why A is occurring now is that we are loading the value of age before we call print. Once the load has occurred, that load is the last use of the memory location.

Yep. That was my intuition for writing this in the "Why?" section for A in the table:

tmp = alice.age
alice.deinit()
print(" age:", tmp)

So print(" age:", alice.age) was desugared to:

tmp = alice.age
print(" age:", tmp)

And then some kind of lifetime checker realized that tmp = alice.age is the last usage of alice, so it decided to put alice.deinit() just after it.

  1. Along paths where values are consumed, the lifetime of the consumed value ends at the consuming use.

Oh... I forgot to put this in my post. I even prepared the code snippet.

func eatMe(_ alice: consuming Alice) {
  print(" start")
  print(" age:", alice.age)
  _ = consume alice
  print(" end")
}

Will print:

INIT
 start
 age: 10
DEINIT
 end

This is also true for borrowing use:

func eatMe(_ alice: consuming Alice) {
  func nop(_ a: borrowing Alice) {}

  print(" start")
  print(" age:", alice.age)
  nop(alice) // <--- deinit goes here
  print(" end")
}

The only gotha here is that even if the function is empty is still has an effect of extending the lifetime of the borrowing argument, so it cannot be optimized out too early by the compiler. Anyway, it works correctly on swift-5.9-DEVELOPMENT-SNAPSHOT-2023-06-05. And the _ = consume Alice is the most idiomatic way of doing things anyway.

  1. Along paths where the value is not-consumed, we extend the lifetime of the value from the last non-consumed site to the end of the lexical scope, only stopping if we reach a point in the program that is reachable from a different code path where the value is consumed. The reason for this is it prevents the compiler from needing to synthesize logic that at runtime determines whether we need to clean up the value depending on the dynamic path that the program takes.

Ok, so we have scope based lifetime.

Btw. a good realization about the branching situation where we have to make lifetimes equal if one of them was consuming. I completely forgot about this.

The only gotcha for scope based lifetime (apart from the branching part) would be introducing the longer lifetime for FileDescriptors and accidentally crashing because we can't open new files (though this would be an programmer error, not something wrong on the Swift part).

This could be solved with:

// THIS IS CODE FOR SCOPE BASED LIFETIME.
// NOT FOR THE CURRENT LAST-USE IMPLEMENTATION!
func eatMe(_ alice: consuming Alice) {
  print(" start")

  do {
    let x = alice // 'alice' is no mode, 'x' is now a thing
    print(" age:", x.age)
    // implicit: x.deinit()
  }

  print(" end")
}
2 Likes

The consume operator works with noncopyable types, so you can also still write print("age: ", (consume x).age) to explicitly end x's lifetime as part of accessing age.

8 Likes

The programming model is easy to remember:

Copyable value lifetimes are optimized. As a result, unnecessary copies are naturally eliminated.

NonCopyable value lifetimes are not optimized. As a result, the behavior of struct deinitializers is consistent across Debug and Release builds.

The fact that the move-checker finds optimal lifetimes for the purpose of diagnostics is an only implementation detail. The implementation then needs to "maximize" the optimized lifetimes within certain constraints, but the overall effect is to actually preserve the original lexical scope. That step is currently missing from the in-tree implementation. It's a bug.

Essentially, although in neither the Copyable nor NonCopyable case do we guarantee that the order that deinitializers run will be preserved.

5 Likes

With latest tool chains I am also getting this error. Also I tried running the latest linux toolchain in docker and getting build error there as well:

Building for debugging...
error: compile command failed due to signal 6 (use -v to see invocation)
swift-frontend: /home/build-user/swift/lib/IRGen/GenType.cpp:2896: bool tryEmitDeinitCall(swift::irgen::IRGenFunction &, swift::SILType, llvm::function_ref<void (Explosion &)>, llvm::function_ref<Address ()>, llvm::function_ref<void ()>): Assertion `deinit && "type has a deinit declared in AST but SIL deinit record is not present!"' failed.
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
0.      Program arguments: /usr/bin/swift-frontend -frontend -c -primary-file /workspaces/OwenerShip/Sources/app/main.swift -emit-dependencies-path /workspaces/OwenerShip/.build/aarch64-unknown-linux-gnu/debug/app.build/main.d -emit-reference-dependencies-path /workspaces/OwenerShip/.build/aarch64-unknown-linux-gnu/debug/app.build/main.swiftdeps -target aarch64-unknown-linux-gnu -Xllvm -aarch64-use-tbi -disable-objc-interop -I /workspaces/OwenerShip/.build/aarch64-unknown-linux-gnu/debug -color-diagnostics -enable-testing -g -module-cache-path /workspaces/OwenerShip/.build/aarch64-unknown-linux-gnu/debug/ModuleCache -swift-version 5 -Onone -D SWIFT_PACKAGE -D DEBUG -new-driver-path /usr/bin/swift-driver -entry-point-function-name app_main -empty-abi-descriptor -plugin-path /usr/lib/swift/host/plugins -plugin-path /usr/local/lib/swift/host/plugins -resource-dir /usr/lib/swift -enable-anonymous-context-mangled-names -Xcc -fPIC -module-name app -package-name owenership -o /workspaces/OwenerShip/.build/aarch64-unknown-linux-gnu/debug/app.build/main.swift.o -index-store-path /workspaces/OwenerShip/.build/aarch64-unknown-linux-gnu/debug/index/store -index-system-modules
1.      Swift version 5.9-dev (LLVM 464b04eb9b157e3, Swift 7203d52cb1e074d)
2.      Compiling with the current language version
3.      While evaluating request IRGenRequest(IR Generation for file "/workspaces/OwenerShip/Sources/app/main.swift")
4.      While emitting IR SIL function "@$s3app8someFuncyyF".
 for 'someFunc()' (at /workspaces/OwenerShip/Sources/app/main.swift:4:1)
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
/usr/bin/swift-frontend[0x6136924]

This is the code I am trying running, in library target:

public struct SomeStruct: ~Copyable {
    public let val: Int

    public init(val: Int) {
        self.val = val
    }

    deinit {
        print("deinit called")
    }
}

And in executable target:

func someFunc() {
    let val = SomeStruct(val: 6)
    print(val.val)
}

Surprisingly the error is gone if I move the SomeStruct type in the executable target, the error happens only if it is in a separate target. Is this a bug?

This was fixed recently. We just have not had a snapshot for a week or so due to issues on other platforms. Once we get another snapshot (something after June 7th), I would try again.

That being said, for things like this, could you please file a GitHub issue rather than using the accepted thread. That is the appropriate place for reports like this.

3 Likes

It depends on the context. Tilde is used as "not" in mathematical set notation and bitwise-not in most C-based languages. It is the former in this case.

1 Like

I once saw ~Copyable described as "maybe Copyable, as it can take Copyable and not Copyable values, and is not limited to only not Copyable. So the sigil is a bit misleading but the best choice, ?, probably isn't usable there.

It would be very strange for ?Copyable to mean something different from Copyable?, which is shorthand for (any Copyable)?. Any objections about the ambiguity of ~Copyable are equally applicable to ~0, which can read as “approximately zero`. That’s not going to change any time soon.

Not any more than it means something different for !value vs. value!. Nor do I think that ~Copyable makes much sense to be read as "approximately Copyable". I was simply objecting to the characterization of it as "not Copyable, as that's not really what it is.

3 Likes

Lots of prior art for reusing symbols in Swift. I think it is inevitable considering the limited number of symbols on the keyboard.