Yeah, this gave me pause too. If you think it through, autoclosures really shouldn’t make this possible.
An autoclosure takes something that looks like a normal value and makes it a closure instead, assigning an expression of type T
to a parameter of type () -> T
. This code:
func expensiveComputation() -> T { … }
@async let foo: T = { expensiveComputation() }
…is the opposite of what an autoclosure does: it’s taking a closure of type () -> T
and assigning to a variable (the implicit wrappedValue
parameter of the property wrapper) of type T
.
But but but…is it possible to finagle a property wrapper it to make this line of code work if we use trickery inside the property wrapper, say, making wrappedValue
a computed property?
@Foo let val: T = { expensiveComputation() }
TL;DR: No, it’s not possible. And it doesn’t support `let` either. (Expand for details)
I did some experimenting, and property wrappers are pretty clear about this, as it turns out:
@propertyWrapper struct Foo<T> {
private var wrappedValueDeferred: () -> T
var wrappedValue: T {
get {
wrappedValueDeferred()
}
set {
wrappedValueDeferred = { newValue }
}
}
init(wrappedValue: @escaping () -> T) { // ❌ error: 'init(wrappedValue:)' parameter type ('() -> T') must be the same as its 'wrappedValue' property type ('T') or an @autoclosure thereof
self.wrappedValueDeferred = wrappedValue
}
}
Ah, yes, but to the original question: can we make this work with @autoclosure
? Almost! This compiles:
@propertyWrapper struct Foo<T> {
private var wrappedValueDeferred: () -> T
var wrappedValue: T {
get {
wrappedValueDeferred()
}
set {
wrappedValueDeferred = { newValue }
}
}
init(wrappedValue: @autoclosure @escaping () -> T) { // ✅ OK
self.wrappedValueDeferred = wrappedValue
}
}
…but then when you use that property wrapper:
@Foo let val: T = { expensiveComputation() } // ❌ error: property wrapper can only be applied to a ‘var'
Oops, OK, well, as the proposal states, they chose not to support async var
for a reason, but let’s go with it:
@Foo var val: T = { expensiveComputation() } // ❌ error: cannot convert value of type '() -> T' to specified type 'T'
…because, oops, right, autoclosure is doing the opposite of what we want. This works:
@Foo var val: T = expensiveComputation() // ✅ no braces = OK; expensiveComputation() is secretly deferred
…but adding the braces was the whole point.
In short, property wrappers as constituted in the language now really aren’t a fit for the syntax Chris wants — just as Doug stated. (And I guess he should know!)