Some good solutions to the general problem have already been given – if you just want a way to compare two Car
values for equality, then overloading ==
or defining your own equality method, as shown by @Vyacheslav and @vadian respectively, is a quick and simple way to go. However note that this isn't an actual conformance to Equatable
, and therefore won't compose for example with conditional conformances – i.e you won't be able to then compare two [Car]
values without defining another equality overload.
The more general solution to the problem, as shown by @BohdanSavych, is to build a wrapper type that provides the conformance to Equatable
. This requires more boilerplate, but generally composes better.
It's worth noting that the inability to use protocols with associated types as actual types is just a current limitation of the Swift language – a limitation that will likely be lifted in future versions with generalised existentials.
However it often helps in situations like this to consider whether your data structures can be reorganised to eliminate the need for a protocol to begin with, which can eliminate the associated complexity. Rather than modelling individual manufacturers as separate types – how about modelling a manufacturer as a type, and then have a property of this type on a single Car
structure?
For example:
struct Car : Hashable {
struct ID : Hashable {
let rawValue: Int
}
let id: ID
struct Manufacturer : Hashable {
var name: String
var country: String // may want to consider lifting into a "Country" type
}
let manufacturer: Manufacturer
let name: String
}
extension Car.Manufacturer {
static let bmw = Car.Manufacturer(name: "BMW", country: "Germany")
static let toyota = Car.Manufacturer(name: "Toyota", country: "Japan")
}
extension Car {
static let bmwX6 = Car(
id: ID(rawValue: 101), manufacturer: .bmw, name: "X6"
)
static let toyotaPrius = Car(
id: ID(rawValue: 102), manufacturer: .toyota, name: "Prius"
)
}
let cars: [Car] = [.bmwX6, .toyotaPrius]
print(cars[0] != cars[1]) // true
Here we're taking advantage of the automatic Hashable
synthesis introduced in SE-0185 for Swift 4.1, which will consider all of Car
's stored properties for equality. If you want to refine this to only consider the id
, you can provide your own implementation of ==
and hashValue
(just be sure to enforce the invariant that if x.id == y.id
, then all the other properties are equal).
Given that the conformance is so easily synthesised, IMO there's no real reason to just conform to Equatable
rather than Hashable
in this case.
A couple of other noteworthy things in the above example:
Using a ID
nested structure to represent the id
property instead of a plain Int
. It doesn't make sense to perform Int
operations on such a value (what does it mean to subtract two identifiers?), and you don't want to be able to pass a car identifier to something that for example expects a pizza identifier. By lifting the value into its own strong nested type, we can avoid these issues (Rob Napier has a great talk that uses this exact example).
Using convenience static
properties for common values. This lets us for example define the manufacturer BMW once and then re-use the value across different car models that they make.