Protocol-typed values are represented using an 'existential container' (see this great WWDC talk on them; or on Youtube), which consists of a value-buffer of fixed size in order to store the value (if the value size exceeds this, it'll heap allocate), a pointer to the protocol witness table in order to lookup method implementations and a pointer to the value witness table in order to manage the lifetime of the value.
Unspecialised generics use pretty much the same format (I go into this in slightly more depth in this Q&A) – when they're called, pointers to the protocol and value witness tables are passed to the function, and the value itself is stored locally inside the function using a value-buffer, which will heap allocate for values larger than that buffer.
Therefore, because of the sheer similarity in how these are implemented, we can draw the conclusion that not being able to talk in terms of protocols with associated types or Self
constraints outside of generics is just a current limitation of the language. There's no real technical reason why it's not possible, it just hasn't been implemented (yet).
Here's an excerpt from the Generics Manifesto on "Generalized existentials", which discusses how this could work in practice:
The restrictions on existential types came from an implementation
limitation, but it is reasonable to allow a value of protocol type
even when the protocol has Self constraints or associated types. For
example, consider IteratorProtocol
again and how it could be used as
an existential:
protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
let it: IteratorProtocol = ...
it.next() // if this is permitted, it could return an "Any?", i.e., the existential that wraps the actual element
Additionally, it is reasonable to want to constrain the associated
types of an existential, e.g., "a Sequence
whose element type is
String
" could be expressed by putting a where clause into
protocol<...>
or Any<...>
(per "Renaming protocol<...>
to Any<...>
"):
let strings: Any<Sequence where .Iterator.Element == String> = ["a", "b", "c"]
The leading .
indicates that we're talking about the dynamic type,
i.e., the Self
type that's conforming to the Sequence
protocol.
There's no reason why we cannot support arbitrary where
clauses within
the Any<...>
.
And from being able to type a value as a protocol with an associated type, it's but a short step to allow for type-casting to that given type, and thus allow something like your first extension to compile.