Synthesized Comparable conformance for enums
Swift 5.3 comes with a nice addition for enums: it will synthesize the required implementation to conform to the Comparable
protocol. This reduces a lot the boilerplate needed to make your enums comparable.
Subscribe to my channel to be notified when new videos about Swift Evolution are released.
Up until now there were multiple ways of adopting conformance to this protocol, each one with different degrees of inconvenience.
Even if Swift 5.3 solves all of them, it’s still useful to know these techniques for the rare cases when you want to implement the conformance manually in order to customise some behaviour.
The first trick and the one that requires less code is to bake your enum
with a comparable raw value, like an Int
.
enum Priority: Int, Comparable {
case low
case medium
case high
Having access to an integer makes it really easy to implement the comparison function.
static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
The problem with this is that now the users of your type will be able to treat it like an integer by accessing its raw value. This is something that you rarely want and it should be avoided.
## Private Raw Value
The alternative is to keep the raw value private. This can’t be done automatically so it adds some boilerplate.
private var comparisonValue: Int {
switch self {
case .low:
return 0
case .medium:
return 1
case .high:
return 2
}
}
static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.comparisonValue < rhs.comparisonValue
}
This lets you keep the comparison function simple and without opening the type to misuse.
Hardcore comparison
Another alternative, and probably the most correct one, is to implement the comparison without using any proxy value, by just checking the cases themselves.
private static func minimum(_ lhs: Self, _ rhs: Self) -> Self {
switch (lhs, rhs) {
case (.low, _), (_, .low):
return .low
case (.medium, _), (_, .medium):
return .medium
case (.high, _), (_, .high):
return .high
}
}
static func < (lhs: Self, rhs: Self) -> Bool {
return (lhs != rhs) && (lhs == Self.minimum(lhs, rhs))
}
Conclusion
As you can see the automatic synthesis of Swift 5.3 it’s very welcomed. Without it we can’t avoid adding some boilerplate and even hurting the API of our type. You also have to consider that if your enum has associated values it adds another layer of complexity.
So use Swift 5.3 features when possible, but keep these techniques on your toolbelt for those rare cases where they may be necessary.