I'm trying to implement a closure that can only be called one time.
Doing that I met weird compiler behaviour, which I describe in the comments below.
@propertyWrapper
struct OnceCalledClosureWrapper: ~Copyable {
private var closure: (@Sendable () -> Void)!
@inlinable consuming func callAsFunction() -> Void {
closure()
closure = nil
}
// compiler error: 'consuming' may only be used on 'func' declarations
// But why? Computed var is effectively a function
// Because of this error $-prefixed-syntax `$completion()` call is impossible.
// Workaround is to call private `_completion()`
consuming var projectedValue: Self { self }
// or
// Error: 'self' is borrowed and cannot be consumed.
// Why is it borrowed despite explicit `consuming get`?
var projectedValue: Self {
consuming get { self }
}
// No warnings in source editor or in compile logs :(
// Is it a bug?
@available(*, deprecated, message: "use _completion unstead")
var wrappedValue: @Sendable () -> Void { closure }
// causes compiler error in func declaration: "'wrappedValue' is unavailable:"
@available(*, unavailable, message: "use _completion unstead")
var wrappedValue: @Sendable () -> Void { closure }
var wrappedValue: @Sendable () -> Void {
// No warnings when compiling :( 💥 Runtime crash
@available(*, unavailable, message: "use _completion unstead")
get { fatalError() }
set {}
}
init(wrappedValue: consuming @escaping @Sendable () -> Void) {
self.closure = wrappedValue
}
deinit {
if let closure = closure {
print("closure was not called so do it on deinit")
closure()
}
}
}
func foo(@OnceCalledClosureWrapper completion: @escaping @Sendable () -> Void) {
// _completion = OnceCalledClosureWrapper(wrappedValue: { })
// Error: Cannot assign to value: '_completion' is immutable – OK, it is expected
// But if '_completion' is immutable, how is it mutated inside `callAsFunction()`?
if Bool.random() {
_completion()
} else {
DispatchQueue.global().async { [_completion] in
_completion() // Error: Noncopyable '_completion' cannot be consumed when captured by an escaping closure
// can it be fixed?
}
}
// _completion() – an error if uncommented
// this is what I want
}
foo {
print("once called")
}
Finally, the wrapper is done and works as expected. But the lack of compiler diagnostics make it useless.
Further idea is to add a generic closure argument, as lots of completion handlers are something like (Result) -> Void
.
Are there workarounds? Or will it be possible in Swift 6?