Reputation: 12178
Given this example code:
private protocol P {}
final private class X {
private func j(j: (P) -> Void) -> Void {}
private func jj<Z: P>(jj: (Z) -> Void) -> Void {
j(j: jj)
}
}
Swift 4 in XCode 9.1 gives this compiler error on the line j(j: jj)
:
Cannot convert value of type ‘(Z) -> Void’ to expected argument type ‘(P) -> Void’.
Why?
Note, it seems to me that it should not give this error, because the type constraint <Z: P>
requires that Z absolutely must conform to protocol P. So, there should be absolutely no reason to convert from Z to P, since Z already conforms to P.
Seems like a compiler bug to me...
Upvotes: 1
Views: 273
Reputation: 80781
The compiler is correct – a (Z) -> Void
is not a (P) -> Void
. To illustrate why this is the case, let's define the following conformances:
extension String : P {}
extension Int : P {}
Now let's substitute Int
for Z
:
final private class X {
func j(j: (P) -> Void) {
j("foob")
}
func jj(jj: (Int) -> Void) {
// error: Cannot convert value of type '(Int) -> Void' to expected argument
// type '(P) -> Void'
j(j: jj)
}
}
We cannot pass an (Int) -> Void
to a (P) -> Void
. Why? Well a (P) -> Void
accepts anything that conforms to P
– for example, we could pass in a String
. But the function that we're passing to j
is actually an (Int) -> Void
, so we're trying to pass a String
to an Int
parameter, which is clearly unsound.
If we put the generics back in, it should still be fairly clear why this cannot work:
final private class X {
func j(j: (P) -> Void) {
j("foob")
}
func jj<Z : P>(jj: (Z) -> Void) {
// error: Cannot convert value of type '(Z) -> Void' to expected argument
// type '(P) -> Void'
j(j: jj)
}
}
X().jj { (i: Int) in
print(i) // What are we printing here? A String gets passed in the above implementation..
}
(P) -> Void
is a function can deal with any P
conforming argument. However (Z) -> Void
is a function that can only deal with one specific concrete typed argument that conforms to P
(e.g Int
in our above example). Typing it as a function that can deal with any P
conforming argument would be a lie.
Put in more technical manner, (Z) -> Void
is not a subtype of (P) -> Void
. Functions are contravariant with respect to their parameter types, meaning that (U) -> Void
is a subtype of
(V) -> Void
if and only if V
is a subtype of U
. But P
is not a subtype of Z : P
– Z
is a placeholder that is replaced at runtime with a concrete type that conforms to (so is a subtype of) P
.
The more interesting part comes when we consider the opposite; that is, passing a (P) -> Void
to a (Z) -> Void
. Although the placeholder Z : P
can only be satisfied by a concrete subtype of P
, we cannot substitute P
for Z
because protocols don't conform to themselves.
Upvotes: 1