Recently, I came across a case (pun intended) where I needed to compare two instances of an enum
type in Swift. However, it was an enum
where some cases had associated values. At first glance, it is not obvious how to do this.
Update 03 January 2021
As of Swift 4.1 (proposal SE-0185) the compiler will synthesize Equatable
conformance for you. Associated values will also need to conform to Equatable
.
As you likely know, if you want to compare two instances of a type in Swift, then that type must conform to the Equatable protocol. In other words, you must define the ==
operator for the type.
If the enumeration does not have associated values or if it has a raw-value type, then you get the ==
operator for free from the Swift Standard Library. For example:
// With a raw-value
// Double conforms to Equatable
enum Math: Double {
case Pi = 3.1415
case Phi = 1.6180
case Tau = 6.2831
}
Math.Pi == Math.Pi // true
Math.Tau == Math.Phi // false
Math.Tau != Math.Phi // true
// Without a raw-value
enum CompassPoint: Equatable {
case North
case South
case East
case West
}
CompassPoint.North == CompassPoint.North // true
CompassPoint.South == CompassPoint.East // false
Comparing cases in these enumerations works out-of-the-box because enumerations that have cases of a raw-value type implicitly conform to the RawRepresentable protocol. The Swift Standard Library provides implementations of the ==
operator for RawRepresentable
types and generic T
types.
// Used to compare 'Math' enum
func ==<T : RawRepresentable where T.RawValue : Equatable>(lhs: T, rhs: T) -> Bool
// Used to compare 'CompassPoint' enum
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
It is easy to see how and why this works. For the RawRepresentable
type, as long as the rawValue
conforms to Equatable
, then all this function has to do is compare the raw-value from each type. Without a raw-value, the different enumeration members are fully-fledged values in their own right. But if the some cases of the enumeration have associated values, then you must implement the ==
operator yourself. Consider the following example.
enum Barcode {
case UPCA(Int, Int)
case QRCode(String)
case None
}
// Error: binary operator '==' cannot be applied to two Barcode operands
Barcode.QRCode("code") == Barcode.QRCode("code")```
If you are well versed in Swift's pattern matching capabilities, then conforming to `Equatable` is very straightforward.
```swift
extension Barcode: Equatable {
}
func ==(lhs: Barcode, rhs: Barcode) -> Bool {
switch (lhs, rhs) {
case (let .UPCA(codeA1, codeB1), let .UPCA(codeA2, codeB2)):
return codeA1 == codeA2 && codeB1 == codeB2
case (let .QRCode(code1), let .QRCode(code2)):
return code1 == code2
case (.None, .None):
return true
default:
return false
}
}
Barcode.QRCode("code") == Barcode.QRCode("code") // true
Barcode.UPCA(1234, 1234) == Barcode.UPCA(4567, 7890) // false
Barcode.None == Barcode.None // true
Even with Swift 2.0, the syntax is a somewhat difficult to read and difficult to remember. We must pattern match on each case and then unpack the associated values (if any) to compare them directly. That’s it! Now we can compare our custom enumerations.