I used the following code to implement a wrapper type. However the compiler complained "'self' used after consume":
import Foundation
public struct Wrapper<T: ~Copyable>: ~Copyable {
private var core: T
@usableFromInline
init(_ core: consuming T) {
self.core = core
}
@usableFromInline
consuming func get() -> T {
return core
}
}
public extension Data {
var ext: Wrapper<Data> {
@inlinable
@inline(__always)
_read {
yield Wrapper(self)
}
@inlinable
@inline(__always)
_modify {
var wrapper = Wrapper(consume self)
// self = Data()
yield &wrapper // <- error: : 'self' used after consume
self = wrapper.get()
}
}
}
Currently, I can workaround this issue by uncommenting that self = Data() line, however I'm confused why that's necessary.
This is my thoughts: when the caller start calling this _modify accessor, the caller has already gained exclusive ownership of self, and during the period of yielding &wrapper, Swift's Law of Exclusivity should forbid any overlapping references to self. So it should be safe to leave self in a consumed state during that suspension period, we just need to remember reassigning a new value to self before the accessor fully returns.
Would be glad to hear thoughts from ownership maniacs.
Keep in mind that the part after yield isn't run if the caller throws, so in that case you could end up not reassigning to self. I wonder if using defer instead of writing code after yield would work better?
It's a limitation of the current implementation, documented in "Noncopyable variables captured by escaping closures" section of SE-0390. The following tests use consuming parameter, instead of non-copyable value, for simplicity. Test 1 is similar to the example in SE-0399. Test 2 emulates your usage.
func test1(x: consuming String) {
_ = consume x // error: missing reinitialization of inout parameter 'x' after consume while accessing memory
x = "abc"
let fn: () -> Void = { print(x) }
fn()
}
func test2(x: consuming String) {
let fn: () -> Void = { _ = consume x } // error: missing reinitialization of inout parameter 'x' after consume while accessing memory
fn()
}
PS: I think "inout parameter" in these error messages are incorrect.
PS2: As non-escaping closure doesn't have this issue, it appears that a variable referenced in defer code is considered captured by an escaping closure (that's my intuition too).