swiftgenericstemplate-specialization

How to specialize generic protocol in Swift?


I have this sort of piece of code:

public class Metal {}

public protocol Vehicle<M> {
    associatedtype M
    
    var material: [M] { get set }
}

public protocol Tracktor: Vehicle where M == Metal {}

public protocol MetalTracktor: Tracktor {
    var material: [Metal] { get set }
}

Fails to compile the second definition of var material: [Meta] with error:

Cannot override mutable property 'material' of type '[Self.M]' with covariant type '[Metal]'

Why? What's the right way to specialize Tracktor with Metal type?

I would think that Tracktor is already specialized with Metal type, thus the first and second definitions end up being the same and thus should not clash. What am I missing?


I understand that the second definition is redundant, but Swift usually allows redundant definitions. Why doesn't it in this case?


Solution

  • The Tracktor protocol already specialises Vehicle. You already did it.

    You can write an implementation for Tracktor, and use [Metal] as the type of material.

    public class MyTracktor: Tracktor {
        public var material: [Metal] = []
    }
    

    Swift first determines whether MyTracktor conforms to Vehicle. It infers that the associated type M is Metal. This implicitly creates a typealias declaration:

    typealias M = Metal
    

    Now MyTracktor satisfies both requirements of Vehicle and so conforms to it. It is also the case that MyTracktor.M == Metal, so it conforms to Tracktor.

    Writing var material: [Metal] { get set } in MetalTracktor is not allowed, because in the context of MetalTracktor, M and Metal are two different (though related) types. M is an associated type declared in Vehicle, and Metal is a class type declared at the global scope.

    Compare this to the MyTracktor class above. In MyTracktor, M is a type alias for Metal - M and Metal are the same type. where M == Metal on the other hand, is stating a requirement of Tracktor, not a typealias.

    Swift could technically be designed to be smarter about this, and allow you to write var material: [Metal] { get set } in MetalTracktor, but this is not very useful, since you can just write var material: [M] { get set } to do basically the same thing.