I have an array of different structs, all implementing Equatable
protocol and am trying to pass it to a function that expects a collection where T.Iterator.Element: Equatable
. I know how to solve this problem by using classes and just creating a class Vehicle: Identifiable, Equatable
, and then make Car
and Tractor
implement Vehicle
. However I'd like to know if this is possible with using structs and protocols?
Here's a contrived example of what I'm trying to do
//: Playground - noun: a place where people can play
protocol Identifiable {
var ID: String { get set }
init(ID: String)
init()
}
extension Identifiable {
init(ID: String) {
self.init()
self.ID = ID
}
}
typealias Vehicle = Identifiable & Equatable
struct Car: Vehicle {
var ID: String
init() {
ID = ""
}
public static func ==(lhs: Car, rhs: Car) -> Bool {
return lhs.ID == rhs.ID
}
}
struct Tractor: Vehicle {
var ID: String
init() {
ID = ""
}
public static func ==(lhs: Tractor, rhs: Tractor) -> Bool {
return lhs.ID == rhs.ID
}
}
class Operator {
func operationOnCollectionOfEquatables<T: Collection>(array: T) where T.Iterator.Element: Equatable {
}
}
var array = [Vehicle]() //Protocol 'Equatable' can only be used as a generic constraint because Self or associated type requirements
array.append(Car(ID:"VW"))
array.append(Car(ID:"Porsche"))
array.append(Tractor(ID:"John Deere"))
array.append(Tractor(ID:"Steyr"))
var op = Operator()
op.operationOnCollectionOfEquatables(array: array) //Generic parameter 'T' could not be inferred
The problem is, as the error says, you cannot use protocols with Self or associated type requirements as actual types – as you'd lose the type information for what those requirements were. In this case, you'd lose the type information for the parameters of the
==
implementation – asEquatable
says they must be the same type as the conforming type (i.eSelf
).The solution is almost always to build a type eraser. In the case of expecting types to be equal if their
id
properties are equal, this can be as simple as just storing theid
property and comparing it in the==
implementation.(Note that I renamed your
ID
property toid
in order to conform with Swift naming convention)However, a more general solution would be to store a function in the type eraser that can compare two arbitrary
Vehicle
conforming instances based on their==
implementation, after type-casting to ensure they are the same type as the concrete type that the type eraser was created with.