I know that (Int) -> Void
can't be typecasted to (Any) -> Void
:
let intHandler: (Int) -> Void = { i in
print(i)
}
var anyHandler: (Any) -> Void = intHandler <<<< ERROR
This gives:
error: cannot convert value of type '(Int) -> Void' to specified type '(Any) -> Void'
Question: But I don't know why this work?
let intResolver: ((Int) -> Void) -> Void = { f in
f(5)
}
let stringResolver: ((String) -> Void) -> Void = { f in
f("wth")
}
var anyResolver: ((Any) -> Void) -> Void = intResolver
I messed around with the return type and it still works...:
let intResolver: ((Int) -> Void) -> String = { f in
f(5)
return "I want to return some string here."
}
let stringResolver: ((String) -> Void) -> Void = { f in
f("wth")
}
var anyResolver: ((Any) -> Void) -> Any = intResolver (or stringResolver)
Sorry if this is asked before. I couldn't find this kind of question yet, maybe I don't know the keyword here. Please enlighten me!
If you want to try: https://iswift.org/playground?wZgwi3&v=3
It's all about variance and Swift closures.
Swift is covariant in respect to closure return type, and contra-variant in respect to its arguments. This makes closures having the same return type or a more specific one, and same arguments or less specific, to be compatible.
Thus (Arg1) -> Res1
can be assigned to (Arg2) -> Res2
if Res1: Res2
and Arg2: Arg1
.
To express this, let's tweak a little bit the first closure:
import Foundation
let nsErrorHandler: (CustomStringConvertible) -> NSError = { _ in
return NSError(domain: "", code: 0, userInfo: nil)
}
var anyHandler: (Int) -> Error = nsErrorHandler
The above code works because Int
conforms to CustomStringConvertible
, while NSError
conforms to Error
. Any
would've also work instead of Error
as it's even more generic.
Now that we established that, let's see what happens in your two blocks of code.
The first block tries to assign a more specific argument closure to a less specific one, and this doesn't follow the variance rules, thus it doesn't compile.
How about the second block of code? We are in a similar scenario as in the first block: closures with one argument.
String
, or Void
, is more specific that Any
, so we can use it as return value(Int) -> Void
is more specific than (Any) -> Void
(closure variance rules), so we can use it as argumentThe closure variance is respected, thus intResolver
and stringResolver
are a compatible match for anyResolver
. This sounds a little bit counter-intuitive, but still the compile rules are followed, and this allows the assignment.
Things complicate however if we want to use closures as generic arguments, the variance rules no longer apply, and this due to the fact that Swift generics (with few exceptions) are invariant in respect to their type: MyGenericType<B>
can't be assigned to MyGenericType<A>
even if B: A
. The exceptions are standard library structs, like Optional
and Array
.
First, let's consider exactly why your first example is illegal:
let intHandler: (Int) -> Void = { i in
print(i)
}
var anyHandler: (Any) -> Void = intHandler
// error: Cannot convert value of type '(Int) -> Void' to specified type '(Any) -> Void'
An (Any) -> Void
is a function that can deal with any input; an (Int) -> Void
is a function that can only deal with Int
input. Therefore it follows that we cannot treat an Int
-taking function as a function that can deal with anything, because it can't. What if we called anyHandler
with a String
?
What about the other way around? This is legal:
let anyHandler: (Any) -> Void = { i in
print(i)
}
var intHandler: (Int) -> Void = anyHandler
Why? Because we can treat a function that deals with anything as a function that can deal with Int
, because if it can deal with anything, by definition it must be able to deal with Int
.
So we've established that we can treat an (Any) -> Void
as an (Int) -> Void
. Let's look at your second example:
let intResolver: ((Int) -> Void) -> Void = { f in
f(5)
}
var anyResolver: ((Any) -> Void) -> Void = intResolver
Why can we treat a ((Int) -> Void) -> Void
as an ((Any) -> Void) -> Void
? In other words, why when calling anyResolver
can we forward an (Any) -> Void
argument onto an (Int) -> Void
parameter? Well, as we've already found out, we can treat an (Any) -> Void
as an (Int) -> Void
, thus it's legal.
The same logic applies for your example with ((String) -> Void) -> Void
:
let stringResolver: ((String) -> Void) -> Void = { f in
f("wth")
}
var anyResolver: ((Any) -> Void) -> Void = stringResolver
When calling anyResolver
, we can pass an (Any) -> Void
to it, which then gets passed onto stringResolver
which takes a (String) -> Void
. And a function that can deal with anything is also a function that deals with strings, thus it's legal.
Playing about with the return types works:
let intResolver: ((Int) -> Void) -> String = { f in
f(5)
return "I want to return some string here."
}
var anyResolver: ((Any) -> Void) -> Any = intResolver
Because intResolver
says it returns a String
, and anyResolver
says it returns Any
; well a string is Any
, so it's legal.
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