Equality and the Equatable
protocol in Swift are all about value equality.
In our world of value types, if a = 5
and b = 5
then a
and b
are the same. Any place you pass in a
, you could just as well pass in b
and not notice any difference.
In other words, a == b
.
Reference types can also have equal values but add the additional question of identity and the identity-of operator
===
(triple equals) into the mix. We’ll only consider value equality for this article.
What are the requirements of the Equatable
protocol?
Equatable Protocol
If your type conforms to Equatable
, you can compare two values of the type with ==
and !=
.
The protocol has a single requirement: the ==
function:
public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
It takes two values, and returns a Bool
saying whether they’re equal.
Thanks to a protocol extension, you don’t need to provide !=
as there’s a default implementation:
public static func != (lhs: Self, rhs: Self) -> Bool {
return !(lhs == rhs)
}
If we wanted to screw it up, we could return a random Bool
value each time for inconsistent results. We probably have some intuition that this would be bad, but what are the official invariants we should keep in mind?
According to the documentation, equality comparison results should have these three properties:
- Reflexive: a value always equals itself;
a == a
must always betrue
. - Symmetric: the order of the values doesn’t matter;
a == b
meansb == a
. - Transitive: value relationships chain together; if
a == b
andb == c
thena == c
.
That covers the protocol requirements. How do we implement it for our own types?
Implementing Equatable
Back in the old days, you had to write oftentimes tedious code for ==
:
func == (lhs: Dog, rhs: Dog) -> Bool {
return lhs.name == rhs.name &&
lhs.breed = rhs.breed &&
lhs.age == rhs.age &&
lhs.size == rhs.size &&
// ... etc. 😓
}
Even worse were enumerations with associated values: you had to have a switch, check for each case, bind the values, and then do the ==
dance for each one.
Auto-Equatable
As of Swift 4.1, the compiler can generate the ==
function for you in some cases.
Given this example struct:
struct Puppy: Equatable {
let name: String
let age: Int
}
The compiler will be able to write ==
since both properties, String
and Int
themselves conform to Equatable
.
Conditions for Conformance
What are the requirements to not have to write ==
yourself?
For classes, you’re out of luck; there’s no synthesized conformance and you always have to do it yourself. 😓
For structs, all stored properties must conform to Equatable
. If the struct has no stored properties, then its instances will all equal each other (i.e. ==
will always return true
).
For enumerations, it depends on the associated values.
- If there are no associated values and just bare cases, then you can get
Equatable
conformance. - If there are associated values, then all of their types must conform to
Equatable
.
There’s one extra edge case: as of Swift 4.1, an enumeration with no cases will not get synthesized conformance (although this will change in a future version of Swift).
Of course you can always add your own ==
implementation, and then the compiler won’t bother to auto-generate one for you.
The Closing Brace
Equatable
is a simple protocol and is even easier to work with thanks to automatic conformance in Swift 4.1.
If you’re curious how the compiler does the work to synthesize an implementation, you’re in luck! Check out the article Synthesized Conformance to Equatable for the details.
If you want more protocols, four other protocols build on top of Equatable
:
Learn about one protocol, and more spring up. Turns out it’s protocols all the way down. 🐢
}