The problem is that the local variable s is consumed within a closure, and the compiler can't guarantee that the closure is only executed once, even though that happens to be the case for withLock.
To resolve the issue, you can use an optional and the Optional.take method to assert at runtime that the closure only executes once:
import Synchronization
struct S: ~Copyable {}
final class Storage: Sendable {
let s: Mutex<S?> = .init(.init())
init() {
}
}
struct SHolder: ~Copyable, Sendable {
let s: S
weak var storage: Storage?
deinit {
var s: S? = consume self.s
self.storage?.s.withLock {
$0 = s.take()!
}
}
}
I find the approach is interesting, but I think the reason why Optional.take method works is because it's notconsuming but a regular mutating method. So, from compiler's perspective it's certainly fine to call it in closure. On the other hand, although the method doesn't consume the entire value, it consumes a part of itself (the wrapped value), so it does what OP wants to do even if it's just a regular mutating method.
I understand the reason why you use force-unwrapping in your code, but that kind of runtime assert isn't visible to compiler. For example, if you copy Optional.take method code but define it as a consuming method, it wouldn't compile even if using force-unwrapping.
Side note: I think the fact that var s: S? = consume self.s line compiles proves that deinit closure is considered non-escaping, otherwise it shouldn't compile.
I agree it's clever workaround. What I wanted to point out was that the code compiled not because of "assert at runtime" but because of Optional.take being not consuming. This might be obvious to other people in the forum, but it puzzled me for a while when I first saw the solution
Glad you figured it out yourself . "Turning a consuming operation into a mutating operation on a wrapper" is a typical escape hatch where the language lacks expressivity.
Also, the reason why this line var s: S? = consume self.s is valid, is that there's this rule that we can partially consume a non-copyable aggregate inside its deinit.