Operation on an array of structures implementing Equatable

I have an array of different structures that all implement the Equatable protocol and try to pass it to a function that expects a collection where T.Iterator.Element: Equatable . I know how to solve this problem with classes and just create a class Vehicle: Identifiable, Equatable , and then make Car and Tractor implement Vehicle . However, I would like to know if this is possible using structures and protocols?

Here's a far-fetched 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 
+8
arrays struct swift swift3
source share
1 answer

The problem is that, as the error says, you cannot use protocols with native or related type requirements as real types - because you will lose type information for these requirements. In this case, you will lose type information for the implementation parameters == - since Equatable says that they must be of the same type as the corresponding type (ie Self ).

The solution almost always consists in creating an eraser type . If you expect the types to be equal if their id properties are equal, it can be as simple as saving the id property and comparing it in the == implementation.

 struct AnyVehicle : Equatable { static func ==(lhs: AnyVehicle, rhs: AnyVehicle) -> Bool { return lhs.id == rhs.id } let id : String init<T : Vehicle>(_ base: T) { id = base.id } } 

(Note that I renamed your id property to id to match the Swift naming convention)

However, a more general solution would be to keep the function in the type eraser, which can compare two arbitrary Vehicle corresponding instances based on their implementation == , after casting the types, to ensure that they are the same type as the concrete type, which was created using an eraser type.

 struct AnyVehicle : Equatable { static func ==(lhs: AnyVehicle, rhs: AnyVehicle) -> Bool { // forward to both lhs and rhs _isEqual in order to determine equality. // the reason that both must be called is to preserve symmetry for when a // superclass is being compared with a subclass. // if you know you're always working with value types, you can omit one of them. return lhs._isEqual(rhs) || rhs._isEqual(lhs) } let base: Identifiable private let _isEqual: (_ to: AnyVehicle) -> Bool init<T : Vehicle>(_ base: T) { self.base = base _isEqual = { // attempt to cast the passed instance to the concrete type that // AnyVehicle was initialised with, returning the result of that // type == implementation, or false otherwise. if let other = $0.base as? T { return base == other } else { return false } } } } 

 print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Tractor(id: "foo"))) // false print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "bar"))) // false print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "foo"))) // true var array = [AnyVehicle]() array.append(AnyVehicle(Car(id: "VW"))) array.append(AnyVehicle(Car(id: "Porsche"))) array.append(AnyVehicle(Tractor(id: "John Deere"))) array.append(AnyVehicle(Tractor(id: "Steyr"))) var op = Operator() // compiles fine as AnyVehicle conforms to Equatable. op.operationOnCollectionOfEquatables(array: array) 
+7
source share

All Articles