What are Equatable, Hashable and Comparable, and when does Swift synthesize them?
TL;DR
- Equatable: you can compare two values with
==. - Hashable: the value can produce a hash. Needed for
Setelements andDictionarykeys. It requiresEquatable. - Comparable: you can order values with
<. Needed forsorted(),min(),max().
For structs and enums, Swift writes Equatable and Hashable for you if all stored properties (or associated values) conform.
Equatable
struct Point: Equatable {
var x: Int
var y: Int
}
Point(x: 1, y: 2) == Point(x: 1, y: 2) // trueYou did not write ==. Swift compared every stored property.
Custom version when only some fields matter:
struct User: Equatable {
let id: UUID
var name: String
var lastSeen: Date
static func == (lhs: User, rhs: User) -> Bool {
lhs.id == rhs.id
}
}Hashable
A hash is an Int that summarizes a value. Set and Dictionary use it to find items fast (O(1) on average).
struct Point: Hashable {
var x: Int
var y: Int
}
var visited: Set<Point> = [Point(x: 0, y: 0)]
visited.contains(Point(x: 0, y: 0)) // trueThe one rule
If a == b, then a.hashValue == b.hashValue.
So if you write a custom ==, write a matching hash(into:) with the same properties.
struct User: Hashable {
let id: UUID
var name: String
static func == (lhs: User, rhs: User) -> Bool { lhs.id == rhs.id }
func hash(into hasher: inout Hasher) {
hasher.combine(id) // same field as ==
}
}If you hash name but compare only id, two equal users can land in different buckets. The Set then breaks in strange ways.
Do not save hash values
Swift uses a random seed for hashing on each app launch. hashValue changes between runs. Never store it on disk or send it to a server.
Comparable
Comparable needs <. Swift gives you >, <= and >= for free.
struct Version: Comparable {
let major: Int
let minor: Int
static func < (lhs: Version, rhs: Version) -> Bool {
(lhs.major, lhs.minor) < (rhs.major, rhs.minor)
}
}
let versions = [Version(major: 2, minor: 0), Version(major: 1, minor: 5)]
versions.sorted() // [1.5, 2.0]
versions.max() // 2.0Tuple comparison (up to six elements) is a neat trick for multi-field ordering.
Swift does not synthesize Comparable for structs. It does synthesize it for enums without raw values, in case order:
enum Priority: Comparable {
case low, medium, high
}
Priority.low < Priority.high // trueWhen synthesis happens
| Protocol | Struct | Enum | Class |
|---|---|---|---|
Equatable | Yes, if all properties conform | Yes, if all associated values conform | No |
Hashable | Yes, same rule | Yes, same rule | No |
Comparable | No | Yes, if no raw values (associated values must be Comparable) | No |
Enums without associated values get Equatable and Hashable automatically, even without writing the conformance.
For classes you always write it yourself. Think about whether equality means "same identity" (===) or "same values".
final class Session: Hashable {
let token: String
init(token: String) { self.token = token }
static func == (lhs: Session, rhs: Session) -> Bool { lhs === rhs }
func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) }
}In SwiftUI
ForEachneedsIdentifiableor anid:key path. That is about identity, not equality.NavigationStack(path:)andnavigationDestination(for:)needHashablevalues..onChange(of:)needsEquatableto know if a value changed.
Common follow-up questions
- Why does Hashable inherit from Equatable? Hash tables first find a bucket by hash, then use
==to confirm the match. - What happens on a hash collision? Two different values get the same bucket. The set still works, using
==, but gets slower if it happens a lot. - Is
hashValuedeprecated? Implementing it is deprecated. Implementhash(into:)instead. - Should
IdentifiableandEquatableuse the same field? Not always.idsays "same item".==can say "same item and same content", which helps UI updates. - Can you make a tuple Hashable? Tuples cannot conform to protocols yet, but
==and<work on tuples up to six elements.