Why doesn't this Swift code compile?
protocol P { }
struct S: P { }
let arr:[P] = [ S() ]
extension Array where Element : P {
func test<T>() -> [T] {
return []
}
}
let result : [S] = arr.test()
The compiler says: "Type P
does not conform to protocol P
" (or, in later versions of Swift, "Using 'P' as a concrete type conforming to protocol 'P' is not supported.").
Why not? This feels like a hole in the language, somehow. I realize that the problem stems from declaring the array arr
as an array of a protocol type, but is that an unreasonable thing to do? I thought protocols were there exactly to help supply structs with something like a type hierarchy?
Why don't protocols conform to themselves?
Allowing protocols to conform to themselves in the general case is unsound. The problem lies with static protocol requirements.
These include:
static
methods and propertiesWe can access these requirements on a generic placeholder
T
whereT : P
– however we cannot access them on the protocol type itself, as there's no concrete conforming type to forward onto. Therefore we cannot allowT
to beP
.Consider what would happen in the following example if we allowed the
Array
extension to be applicable to[P]
:We cannot possibly call
appendNew()
on a[P]
, becauseP
(theElement
) is not a concrete type and therefore cannot be instantiated. It must be called on an array with concrete-typed elements, where that type conforms toP
.It's a similar story with static method and property requirements:
We cannot talk in terms of
SomeGeneric<P>
. We need concrete implementations of the static protocol requirements (notice how there are no implementations offoo()
orbar
defined in the above example). Although we can define implementations of these requirements in aP
extension, these are defined only for the concrete types that conform toP
– you still cannot call them onP
itself.Because of this, Swift just completely disallows us from using a protocol as a type that conforms to itself – because when that protocol has static requirements, it doesn't.
Instance protocol requirements aren't problematic, as you must call them on an actual instance that conforms to the protocol (and therefore must have implemented the requirements). So when calling a requirement on an instance typed as
P
, we can just forward that call onto the underlying concrete type's implementation of that requirement.However making special exceptions for the rule in this case could lead to surprising inconsistencies in how protocols are treated by generic code. Although that being said, the situation isn't too dissimilar to
associatedtype
requirements – which (currently) prevent you from using a protocol as a type. Having a restriction that prevents you from using a protocol as a type that conforms to itself when it has static requirements could be an option for a future version of the languageEdit: And as explored below, this does look like what the Swift team are aiming for.
@objc
protocolsAnd in fact, actually that's exactly how the language treats
@objc
protocols. When they don't have static requirements, they conform to themselves.The following compiles just fine:
baz
requires thatT
conforms toP
; but we can substitute inP
forT
becauseP
doesn't have static requirements. If we add a static requirement toP
, the example no longer compiles:So one workaround to to this problem is to make your protocol
@objc
. Granted, this isn't an ideal workaround in many cases, as it forces your conforming types to be classes, as well as requiring the Obj-C runtime, therefore not making it viable on non-Apple platforms such as Linux.But I suspect that this limitation is (one of) the primary reasons why the language already implements 'protocol without static requirements conforms to itself' for
@objc
protocols. Generic code written around them can be significantly simplified by the compiler.Why? Because
@objc
protocol-typed values are effectively just class references whose requirements are dispatched usingobjc_msgSend
. On the flip side, non-@objc
protocol-typed values are more complicated, as they carry around both value and witness tables in order to both manage the memory of their (potentially indirectly stored) wrapped value and to determine what implementations to call for the different requirements, respectively.Because of this simplified representation for
@objc
protocols, a value of such a protocol typeP
can share the same memory representation as a 'generic value' of type some generic placeholderT : P
, presumably making it easy for the Swift team to allow the self-conformance. The same isn't true for non-@objc
protocols however as such generic values don't currently carry value or protocol witness tables.However this feature is intentional and is hopefully to be rolled out to non-
@objc
protocols, as confirmed by Swift team member Slava Pestov in the comments of SR-55 in response to your query about it (prompted by this question):So hopefully it's something that language will one day support for non-
@objc
protocols as well.But what current solutions are there for non-
@objc
protocols?Implementing extensions with protocol constraints
In Swift 3.1, if you want an extension with a constraint that a given generic placeholder or associated type must be a given protocol type (not just a concrete type that conforms to that protocol) – you can simply define this with an
==
constraint.For example, we could write your array extension as:
Of course, this now prevents us from calling it on an array with concrete type elements that conform to
P
. We could solve this by just defining an additional extension for whenElement : P
, and just forward onto the== P
extension:However it's worth noting that this will perform an O(n) conversion of the array to a
[P]
, as each element will have to be boxed in an existential container. If performance is an issue, you can simply solve this by re-implementing the extension method. This isn't an entirely satisfactory solution – hopefully a future version of the language will include a way to express a 'protocol type or conforms to protocol type' constraint.Prior to Swift 3.1, the most general way of achieving this, as Rob shows in his answer, is to simply build a wrapper type for a
[P]
, which you can then define your extension method(s) on.Passing a protocol-typed instance to a constrained generic placeholder
Consider the following (contrived, but not uncommon) situation:
We cannot pass
p
totakesConcreteP(_:)
, as we cannot currently substituteP
for a generic placeholderT : P
. Let's take a look at a couple of ways in which we can solve this problem.1. Opening existentials
Rather than attempting to substitute
P
forT : P
, what if we could dig into the underlying concrete type that theP
typed value was wrapping and substitute that instead? Unfortunately, this requires a language feature called opening existentials, which currently isn't directly available to users.However, Swift does implicitly open existentials (protocol-typed values) when accessing members on them (i.e it digs out the runtime type and makes it accessible in the form of a generic placeholder). We can exploit this fact in a protocol extension on
P
:Note the implicit generic
Self
placeholder that the extension method takes, which is used to type the implicitself
parameter – this happens behind the scenes with all protocol extension members. When calling such a method on a protocol typed valueP
, Swift digs out the underlying concrete type, and uses this to satisfy theSelf
generic placeholder. This is why we're able to calltakesConcreteP(_:)
withself
– we're satisfyingT
withSelf
.This means that we can now say:
And
takesConcreteP(_:)
gets called with its generic placeholderT
being satisfied by the underlying concrete type (in this caseS
). Note that this isn't "protocols conforming to themselves", as we're substituting a concrete type rather thanP
– try adding a static requirement to the protocol and seeing what happens when you call it from withintakesConcreteP(_:)
.If Swift continues to disallow protocols from conforming to themselves, the next best alternative would be implicitly opening existentials when attempting to pass them as arguments to parameters of generic type – effectively doing exactly what our protocol extension trampoline did, just without the boilerplate.
However note that opening existentials isn't a general solution to the problem of protocols not conforming to themselves. It doesn't deal with heterogenous collections of protocol-typed values, which may all have different underlying concrete types. For example, consider:
For the same reasons, a function with multiple
T
parameters would also be problematic, as the parameters must take arguments of the same type – however if we have twoP
values, there's no way we can guarantee at compile time that they both have the same underlying concrete type.In order to solve this problem, we can use a type eraser.
2. Build a type eraser
As Rob says, a type eraser, is the most general solution to the problem of protocols not conforming to themselves. They allow us to wrap a protocol-typed instance in a concrete type that conforms to that protocol, by forwarding the instance requirements to the underlying instance.
So, let's build a type erasing box that forwards
P
's instance requirements onto an underlying arbitrary instance that conforms toP
:Now we can just talk in terms of
AnyP
instead ofP
:Now, consider for a moment just why we had to build that box. As we discussed early, Swift needs a concrete type for cases where the protocol has static requirements. Consider if
P
had a static requirement – we would have needed to implement that inAnyP
. But what should it have been implemented as? We're dealing with arbitrary instances that conform toP
here – we don't know about how their underlying concrete types implement the static requirements, therefore we cannot meaningfully express this inAnyP
.Therefore, the solution in this case is only really useful in the case of instance protocol requirements. In the general case, we still cannot treat
P
as a concrete type that conforms toP
.If you extend
CollectionType
protocol instead ofArray
and constraint by protocol as a concrete type, you can rewrite the previous code as follows.EDIT: Eighteen more months of working w/ Swift, another major release (that provides a new diagnostic), and a comment from @AyBayBay makes me want to rewrite this answer. The new diagnostic is:
That actually makes this whole thing a lot clearer. This extension:
doesn't apply when
Element == P
sinceP
is not considered a concrete conformance ofP
. (The "put it in a box" solution below is still the most general solution.)Old Answer:
It's yet another case of metatypes. Swift really wants you to get to a concrete type for most non-trivial things.(I don't think that's actually true; you can absolutely create something of size[P]
isn't a concrete type (you can't allocate a block of memory of known size forP
).P
because it's done via indirection.) I don't think there's any evidence that this is a case of "shouldn't" work. This looks very much like one of their "doesn't work yet" cases. (Unfortunately it's almost impossible to get Apple to confirm the difference between those cases.) The fact thatArray<P>
can be a variable type (whereArray
cannot) indicates that they've already done some work in this direction, but Swift metatypes have lots of sharp edges and unimplemented cases. I don't think you're going to get a better "why" answer than that. "Because the compiler doesn't allow it." (Unsatisfying, I know. My whole Swift life…)The solution is almost always to put things in a box. We build a type-eraser.
When Swift allows you to do this directly (which I do expect eventually), it will likely just be by creating this box for you automatically. Recursive enums had exactly this history. You had to box them and it was incredibly annoying and restricting, and then finally the compiler added
indirect
to do the same thing more automatically.