Why can we not cast to protocol types with associated types but achieve the same effect using generics?
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, considerIteratorProtocol
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., "aSequence
whose element type is
String
" could be expressed by putting a where clause into
protocol<...>
orAny<...>
(per "Renamingprotocol<...>
toAny<...>
"):let strings: Any<Sequence where .Iterator.Element == String> = ["a", "b", "c"]
The leading
.
indicates that we're talking about the dynamic type,
i.e., theSelf
type that's conforming to theSequence
protocol.
There's no reason why we cannot support arbitrarywhere
clauses within
theAny<...>
.
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.
In Swift, how to cast to protocol with associated type?
Unfortunately, Swift doesn't currently support the use of protocols with associated types as actual types. This however is technically possible for the compiler to do; and it may well be implemented in a future version of the language.
A simple solution in your case is to define a 'shadow protocol' that SpecialController
derives from, and allows you to access currentValue
through a protocol requirement that type erases it:
// This assumes SpecialValue doesn't have associated types – if it does, you can
// repeat the same logic by adding TypeErasedSpecialValue, and then using that.
protocol SpecialValue {
// ...
}
protocol TypeErasedSpecialController {
var typeErasedCurrentValue: SpecialValue? { get }
}
protocol SpecialController : TypeErasedSpecialController {
associatedtype SpecialValueType : SpecialValue
var currentValue: SpecialValueType? { get }
}
extension SpecialController {
var typeErasedCurrentValue: SpecialValue? { return currentValue }
}
extension String : SpecialValue {}
struct S : SpecialController {
var currentValue: String?
}
var x: Any = S(currentValue: "Hello World!")
if let sc = x as? TypeErasedSpecialController {
print(sc.typeErasedCurrentValue as Any) // Optional("Hello World!")
}
Protocol can only be used as a generic constraint because it has Self or associatedType requirements
Suppose for the moment we adjust your protocol to add a routine that uses the associated type:
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
And Swift were to let you create an array of RequestType
the way you want to. I could pass an array of those request types into a function:
func handleQueueOfRequests(queue: [RequestType]) {
// frobulate All The Things!
for request in queue {
request.frobulateModel(/* What do I put here? */)
}
}
I get down to the point that I want to frobulate all the things, but I need to know what type of argument to pass into the call. Some of my RequestType
entities could take a LegoModel
, some could take a PlasticModel
, and others could take a PeanutButterAndPeepsModel
. Swift is not happy with the ambiguity so it will not let you declare a variable of a protocol that has an associated type.
At the same time it makes perfect sense to, for example, create an array of RequestType
when we KNOW that all of them use the LegoModel
. This seems reasonable, and it is, but you need some way to express that.
One way to do that is to create a class (or struct, or enum) that associates a real type with the abstract Model type name:
class LegoRequestType: RequestType {
typealias Model = LegoModel
// Implement protocol requirements here
}
Now it's entirely reasonable to declare an array of LegoRequestType
because if we wanted to frobulate
all of them we know we would have to pass in a LegoModel
each time.
This nuance with Associated Types makes any protocol that uses them special. The Swift Standard Library has Protocols like this most notably Collection
or Sequence
.
To allow you to create an array of things that implement the Collection
protocol or a set of things that implement the sequence protocol, the Standard Library employs a technique called "type-erasure" to create the struct types AnyCollection<T>
or AnySequence<T>
. The type-erasure technique is rather complex to explain in a Stack Overflow answer, but if you search the web there are lots of articles about it.
Swift 5.7 Existentials
Swift 5.7 introduces explicit existential using the any
keyword. This will remove the "Protocol can only be used as a generic constraint…" error, but it doesn't solve the fundamental problem with this example. (Admittedly this example is academic, for demonstration purposes, and likely not useful in real code because of its limitations. But it also demonstrates how explicit existentials aren't a panacea.)
Here is the code sample using Swift 5.7 and the any
keyword.
public protocol RequestType: AnyObject {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
func handleQueueOfRequests(queue: [any RequestType]) {
// frobulate All The Things!
for request in queue {
request.frobulateModel(/* What do I put here? */)
}
}
Now our queue contains a collection of existentials and we no longer have the error about the "type cannot be used here because of Self or AssociatedType constraints". But it doesn't fix the underlying problem in this example because the frobulateModel
method can still take an arbitrary type (the associated type of the entity conforming to the RequestType
protocol).
Swift provides other mechanisms that can help compensate for this. In general you'd want constrain the Model
value to expose behavior shared by all Models
. The frobulateModel
method might be made generic and have constraints on the parameter to follow that protocol. Or you could use Swift 5.7's primary associated types (SE-0346) to help constrain the behavior of Models
at the protocol level.
So yes, explicit existentials can remove the error message that the OP asked about - but they are not a solution for every situation.
Also, keep in mind that existentials lead to indirection and that can introduce performance problems. In their WWDC session, Apple cautioned us to use them judiciously.
Check if two objects implement a Swift protocol and its associated type
It was trickier than I expected (so I deleted my previous post to avoid confusion) but I believe this should work for you:
protocol ViewModelContainerVC
{
mutating func setModel(_ :Any)
}
protocol ViewModelContainer:class,ViewModelContainerVC
{
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
extension ViewModelContainer
{
mutating func setModel(_ model:Any)
{ if model is ViewModelType { viewModel = model as! ViewModelType } }
}
You can then use the ViewModelContainerVC for type casting and assignment:
if let container = container as? ViewModelContainerVC
{
container.setModel(model)
}
[EDIT] for future reference, here's the same thing with a Bool return for type compatibility:
protocol ViewModelContainerVC
{
@discardableResult mutating func setModel(_ :Any) -> Bool
}
extension ViewModelContainer
{
@discardableResult mutating func setModel(_ model:Any) -> Bool
{
if let validModel = model as? ViewModelType
{ viewModel = validModel; return true }
return false
}
}
Which will allow a combined condition :
if var container = container as? ViewModelContainerVC,
container.setModel(model)
{ ... }
Related Topics
Get Button Pressed Id on Swift Via Sender
In Swift 3, How to Calculate the Factorial When the Result Becomes Too High
How to Get User Home Directory Path (Users/"User Name") Without Knowing the Username in Swift3
Multiple Type Constraints in Swift
How to Create a Generic Array Extension That Sums Number Types in Swift
Draw on a PDF Using Swift on MACos
How to Do If Pattern Matching with Multiple Cases
Remove Default Padding from List in Swiftui
Custom Segue Transition Animation
How to Set the Legacy Swift Versions for Each Pod in Podfile
Ios13 Navigation Bar Large Titles Not Covering Status Bar
Multiple Workers in Swift Command Line Tool
Compile Error in Swift 4 on Parameter Passing
@Noescape Attribute in Swift 1.2
Uicollectionview - Resizing Cells on Device Rotate - Swift
Class 'Viewcontroller' Has No Initializers in Swift
Variable 'Xxx' Was Never Mutated, Consider Changing to 'Let'
What Causes 'Constant Captured by a Closure Before Being Initialized' Error