Swift Academy · Lesson

Custom Equality Semantics

Implement domain-correct equality rules.

Lesson 4 of 413 steps

Custom Equality Semantics is a free Swift Academy lesson on CoddyKit. This is lesson 4 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, and your progress syncs across the web and the CoddyKit app. The Swift Academy course includes 4 lessons in total.

When Default == Is Wrong

Synthesized == compares every stored property. Sometimes that is too strict (you only care about an id) or too loose. Then you write == yourself.

Identity-Based Equality

Treat two records as equal when their ids match, ignoring other fields:

struct Account: Equatable {
    let id: Int
    var balance: Int
    static func == (l: Account, r: Account) -> Bool { l.id == r.id }
}
print(Account(id: 1, balance: 10) == Account(id: 1, balance: 999))  // true

Case-Insensitive Equality

Normalize before comparing:

struct Username: Equatable {
    let raw: String
    static func == (l: Username, r: Username) -> Bool {
        l.raw.lowercased() == r.raw.lowercased()
    }
}
print(Username(raw: "Ann") == Username(raw: "ANN"))  // true

Keep Hashable Consistent

If you customize ==, you MUST update hash(into:) to use the same fields, or equal values may hash differently and break Sets.

Matching == and hash(into:)

Both use only id, staying consistent:

struct User: Hashable {
    let id: Int
    var name: String
    static func == (l: User, r: User) -> Bool { l.id == r.id }
    func hash(into h: inout Hasher) { h.combine(id) }
}
print(Set([User(id: 1, name: "A"), User(id: 1, name: "B")]).count)  // 1

Equality That Ignores a Cache

Exclude derived/cached fields from comparison:

struct Doc: Equatable {
    let text: String
    var cachedWordCount: Int
    static func == (l: Doc, r: Doc) -> Bool { l.text == r.text }
}
let a = Doc(text: "hi", cachedWordCount: 1)
let b = Doc(text: "hi", cachedWordCount: 999)
print(a == b)  // true

Tolerant Floating-Point Equality

Compare doubles within an epsilon instead of exactly:

struct Measure: Equatable {
    let value: Double
    static func == (l: Measure, r: Measure) -> Bool {
        abs(l.value - r.value) < 0.0001
    }
}
print(Measure(value: 0.1 + 0.2) == Measure(value: 0.3))  // true

Equality Across Subset of Fields

Compare a meaningful subset for domain equality:

struct Card: Equatable {
    let rank: Int
    let suit: String
    let isFaceUp: Bool   // display state, not identity
    static func == (l: Card, r: Card) -> Bool {
        l.rank == r.rank && l.suit == r.suit
    }
}
print(Card(rank: 1, suit: "H", isFaceUp: true) == Card(rank: 1, suit: "H", isFaceUp: false))  // true

Respect the Equatable Laws

Custom == must stay reflexive (a == a), symmetric (a == b implies b == a), and transitive. Violating these confuses collections and algorithms.

A Symmetric Implementation

Comparing normalized forms keeps symmetry intact:

struct Email: Equatable {
    let address: String
    static func == (l: Email, r: Email) -> Bool {
        l.address.lowercased() == r.address.lowercased()
    }
}
let x = Email(address: "A@x.com")
let y = Email(address: "a@X.com")
print(x == y, y == x)  // true true

Custom Equality in a Set

Because id-only equality and hashing agree, the Set treats same-id values as one:

struct Item: Hashable {
    let id: Int
    var note: String
    static func == (l: Item, r: Item) -> Bool { l.id == r.id }
    func hash(into h: inout Hasher) { h.combine(id) }
}
let s: Set<Item> = [Item(id: 1, note: "x"), Item(id: 1, note: "y")]
print(s.count)  // 1

Quick Check

After writing a custom == that uses only some fields, what must you also do?

Recap

You learned custom equality:

  • Write your own == when default whole-struct comparison is wrong
  • Common cases: id-only, case-insensitive, epsilon floats, ignore caches
  • Keep hash(into:) consistent with the same fields
  • Stay reflexive, symmetric, and transitive

Course complete! Next: CustomStringConvertible and Mirror.

Free to start

Learn Swift with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
122
Lessons
409

Frequently Asked Questions

Is the “Custom Equality Semantics” lesson free?

Yes — any 3 lessons of the Swift Academy learning path, including “Custom Equality Semantics”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Swift Academy course includes 4 lessons in total.

What will I learn in “Custom Equality Semantics”?

Implement domain-correct equality rules. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.

How long does the “Custom Equality Semantics” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Synthesized Equatable
  2. Hashable and hash(into:)
  3. Comparable and Sorting
  4. Custom Equality Semantics
← Back to Swift Academy