Objective-C required us to wax philosophic about the nature of equality and identity. To the relief of any developer less inclined towards discursive treatises, this is not as much the case for Swift.

In Swift, there’s the Equatable protocol, which explicitly defines the semantics of equality and inequality in a manner entirely separate from the question of identity. There’s also the Comparable protocol, which builds on Equatable to refine inequality semantics to creating an ordering of values. Together, the Equatable and Comparable protocols form the central point of comparison throughout the language.

Equatable

Values conforming to the Equatable protocol can be evaluated for equality and inequality. Conformance to Equatable requires the implementation of the equality operator ( == ).

As an example, consider the following Binomen structure:

struct Binomen { let genus : String let species : String } let 🐺 = Binomen ( genus : "Canis" , species : "lupus" ) let 🐻 = Binomen ( genus : "Ursus" , species : "arctos" )

We can add Equatable conformance through an extension, implementing the required type method for the == operator like so:

extension Binomen : Equatable { static func == ( lhs : Binomen , rhs : Binomen ) -> Bool { return lhs . genus == rhs . genus && lhs . species == rhs . species } } 🐺 == 🐺 // true 🐺 == 🐻 // false

Easy enough, right?

Well actually, it’s even easier than that — as of Swift 4.1, the compiler can automatically synthesize conformance for structures whose stored properties all have types that are Equatable . We could replace all of the code in the extension by simply adopting Equatable in the declaration of Binomen :

struct Binomen : Equatable { let genus : String let species : String } 🐺 == 🐺 // true 🐺 == 🐻 // false

The Benefits of Being Equal

Equatability isn’t just about using the == operator — there’s also the != operator! it also lets a value, among other things, be found in a collection and matched in a switch statement.

[ 🐺 , 🐻 ] . contains ( 🐻 ) // true func common Name ( for binomen : Binomen ) -> String ? { switch binomen { case 🐺 : return "gray wolf" case 🐻 : return "brown bear" default : return nil } } common Name ( for : 🐺 ) // "gray wolf"

Equatable is also a requirement for conformance to Hashable , another important type in Swift.

This is all to say that if a type has equality semantics — if two values of that type can be considered equal or unequal – it should conform to Equatable .

The Limits of Automatic Synthesis

The Swift standard library and most of the frameworks in Apple SDKs do a great job adopting Equatable for types that make sense to be. In practice, you’re unlikely to be in a situation where the dereliction of a built-in type spoils automatic synthesis for your own type.

Instead, the most common obstacle to automatic synthesis involves tuples. Consider this poorly-considered Trinomen type:

struct Trinomen { let genus : String let species : ( String , subspecies : String ?) // 🤔 } extension Trinomen : Equatable {} // 🛑 Type 'Trinomen' does not conform to protocol 'Equatable'

As described in our article about Void , tuples aren’t nominal types , so they can’t conform to Equatable . If you wanted to compare two trinomina for equality, you’d have to write the conformance code for Equatable .

…like some kind of animal.

Conditional Conformance to Equality

In addition to automatic synthesis of Equatable , Swift 4.1 added another critical feature: conditional conformance .

To illustrate this, consider the following generic type that represents a quantity of something:

struct Quantity < Thing > { let count : Int let thing : Thing }

Can Quantity conform to Equatable ? We know that integers are equatable, so it really depends on what kind of Thing we’re talking about.

What conditional conformance Swift 4.1 allows us to do is create an extension on a type with a conditional clause. We can use that here to programmatically express that _“a quantity of a thing is equatable if the thing itself is equatable”:

extension Quantity : Equatable where Thing : Equatable {}

And with that declaration alone, Swift has everything it needs to synthesize conditional Equatable conformance, allowing us to do the following:

let one Hen = Quantity < Character > ( count : 1 , thing : "🐔" ) let two Ducks = Quantity < Character > ( count : 2 , thing : "🦆" ) one Hen == two Ducks // false

Conditional conformance is the same mechanism that provides for an Array whose Element is Equatable to itself conform to Equatable : [ 🐺 , 🐻 ] == [ 🐺 , 🐻 ] // true

Equality by Reference

For reference types, the notion of equality becomes conflated with identity. It makes sense that two Name structures with the same values would be equal, but two Person objects can have the same name and still be different people.

For Objective-C-compatible object types, the == operator is already provided from the is Equal: method:

import Foundation class Obj CObject : NSObject {} Obj CObject () == Obj CObject () // false

For Swift reference types (that is, classes), equality can be evaluated using the identity equality operator ( === ):

class Object : Equatable { static func == ( lhs : Object , rhs : Object ) -> Bool { return lhs === rhs } } Object () == Object () // false

That said, Equatable semantics for reference types are often not as straightforward as a straight identity check, so before you add conformance to all of your classes, ask yourself whether it actually makes sense to do so.

Comparable

Building on Equatable , the Comparable protocol allows for values to be considered less than or greater than other values.

Comparable requires implementations for the following operators:

Operator Name < Less than <= Less than or equal to >= Greater than or equal to > Greater than

…so it’s surprising that you can get away with only implementing one of them: the < operator.

Going back to our binomial nomenclature example, let’s extend Binomen to conform to Comparable such that values are ordered alphabetically first by their genus name and then by their species name:

extension Binomen : Comparable { static func < ( lhs : Binomen , rhs : Binomen ) -> Bool { if lhs . genus != rhs . genus { return lhs . genus < rhs . genus } else { return lhs . species < rhs . species } } } 🐻 > 🐺 // true ("Ursus" lexicographically follows "Canis")

Implementing the < operator for types that consider more than one property is deceptively hard to get right the first time. Be sure to write test cases to validate correct behavior.

This is quite clever. Since the implementations of each comparison operator can be derived from just < and == , all of that functionality is made available automatically through type inference.

Contrast this with how Ruby and other languages derive equality and comparison operators from a single operator, <=> (a.k.a the “UFO operator”). A few pitches to bring formalized ordering to Swift have floated around over the years, such as this one, but we haven’t seen any real movement in this direction lately.

Incomparable Limitations with No Equal

Unlike Equatable , the Swift compiler can’t automatically synthesize conformance to Comparable . But that’s not for lack of trying — it’s just not possible.

There are no implicit semantics for comparability that could be derived from the types of stored properties. If a type has more than one stored property, there’s no way to determine how they’re compared relative to one another. And even if a type had only a single property whose type was Comparable , there’s no guarantee how the ordering of that property would relate to the ordering of the value as a whole

Comparable Benefits

Conforming to Comparable confers a multitude of benefits.

One such benefit is that arrays containing values of comparable types can call methods like sorted() , min() , and max() :

let 🐬 = Binomen ( genus : "Tursiops" , species : "truncatus" ) let 🌻 = Binomen ( genus : "Helianthus" , species : "annuus" ) let 🍄 = Binomen ( genus : "Amanita" , species : "muscaria" ) let 🐶 = Binomen ( genus : "Canis" , species : "domesticus" ) let menagerie = [ 🐺 , 🐻 , 🐬 , 🌻 , 🍄 , 🐶 ] menagerie . sorted () // [🍄, 🐶, 🐺, 🌻, 🐬, 🐻] menagerie . min () // 🍄 menagerie . max () // 🐻

Having a defined ordering also lets you create ranges, like so:

let less Than10 = ..< 10 less Than10 . contains ( 1 ) // true less Than10 . contains ( 11 ) // false let one To Five = 1 ... 5 one To Five . contains ( 3 ) // true one To Five . contains ( 7 ) // false