Consider the following function:
func whatever(foo: @autoclosure () -> Int) {
let x = foo()
print(x)
}
Naturally, we can invoke it like this:
whatever(foo: 5)
// prints: 5
However providing an explicit closure argument causes the compiler to complain:
whatever(foo: { 5 })
// Error: Function produces expected type 'Int'; did you mean to call it with '()'?
Is this the intended? Reading the documentation for @autoclosure I did not find a statement about whether arguments are always wrapped, even when providing a closure. My understanding of @autoclosure was:
Take a closure argument. If the argument is not a closure but has the same type as the closure would return, wrap it.
However, the behaviour I'm seeing is: Wrap the argument no matter what.
A more elaborate example makes this seem very odd to me:
struct Defaults {
static var dispatcher: Defaults = ...
subscript<T>(setting: Setting<T>) -> T { ... }
struct Setting<T> {
let key: String
let defaultValue: () -> T
init(key: String, defaultValue: @escaping @autoclosure () -> T) {
self.key = key
self.defaultValue = defaultValue
}
}
}
extension Defaults.Setting {
static var nickname: Defaults.Setting<String> {
return Defaults.Setting(key: "__nickname", defaultValue: "Angela Merkel")
}
}
// Usage:
Defaults.dispatcher[.nickname] = "Emmanuel Macron"
Now let's say I want to hash the key of a Setting value:
extension Defaults.Setting {
var withHashedKey: Defaults.Setting<T> {
return Defaults.Setting(key: key.md5(), defaultValue: defaultValue)
// Error: Cannot convert return expression of type 'Defaults.Setting<() -> T>' to return type 'Defaults.Setting<T>'
}
}
To clarify: defaultValue is of type () -> T. Providing it to init(key: String, defaultValue: () -> T), in my expectation should just work, because the argument and parameter have the same type (while parameter is @autoclosure).
However, Swift seems to wrap the provided closure, effectively creating () -> () -> T, which creates Setting<() -> T> instead of Setting<T>.
I can work around this issue by declaring an init which takes an explicitly non-@autoclosure parameter:
extension Defaults.Setting {
init(key: String, defaultValue: @escaping () -> T) {
self.init(key: key, defaultValue: defaultValue)
}
}
What's really daunting is that I can simply forward to the init taking the @autoclosure parameter and it works.
Am I missing something here or is it just not possible by design in Swift to provide closure arguments to @autoclosure parameters?
Swift expects you to pass an expression that results in an Int to whatever(foo:) and Swift will wrap that expression in a closure of type () -> Int.
func whatever(foo: @autoclosure () -> Int) {
let x = foo()
print(x)
}
When you call it like this:
func whatever(foo: {5})
you are passing an expression that results in () -> Int and not the Int Swift expects. That is why it suggests you add () and call that closure to get an expression that returns an Int:
func whatever(foo: {5}())
Note that since Swift wraps {5}() in a closure, it does not get evaluated before the call to whatever(foo:) but in fact the call is delayed until you evaluate let x = foo().
You can verify this by running this in a Playground:
func whatever(foo: @autoclosure () -> Int) {
print("inside whatever")
let x = foo()
print(x)
let y = foo()
print(y)
}
whatever(foo: { print("hi"); return 3 }())
Output:
inside whatever hi 3 hi 3
If you want whatever(foo: to also be able to take a () -> Int closure, overload it and call the autoclosure version after calling foo:
func whatever(foo: @autoclosure () -> Int) {
print("autoclosure whatever")
let x = foo()
print(x)
}
func whatever(foo: () -> Int) {
print("closure whatever")
whatever(foo: foo())
}
whatever(foo: { print("two"); return 6 })
whatever(foo: { print("two"); return 6 }())
Output:
closure whatever autoclosure whatever two 6 autoclosure whatever two 6
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With