# How to implement the same Hashability for different values?

**URL:** <https://forums.swift.org/t/how-to-implement-the-same-hashability-for-different-values/78974>\
**Category:** Using Swift\
**Tags:** hashable\
**Created:** [March 31, 2025, 6:52pm UTC](https://forums.swift.org/t/how-to-implement-the-same-hashability-for-different-values/78974 "2025-03-31T18:52:03Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![ylorn](https://avatars.discourse-cdn.com/v4/letter/y/13edae/32.png) [@ylorn](https://forums.swift.org/u/ylorn)\
**Post date:** [March 31, 2025, 6:52pm UTC](https://forums.swift.org/t/how-to-implement-the-same-hashability-for-different-values/78974/1 "2025-03-31T18:52:04Z")

</div>

Let's say we have a type `Foo` that can be created locally or delivered from cloud.

```swift
struct Foo: Hashable, Sendable {
    public enum ID: Hashable, Sendable {
        case local(String)
        case cloud(String)
        case whole(local: String, cloud: String)
    }

    let id: ID
}

```

It holds an `id` property that has 3 cases, which represent 2 workflows.

1. If it gets created locally first, and synchronized with cloud later, then it will be like:

```swift
/// created locally first
var fooLocal = Foo(id: .local("aaaa"))
/// synchronized with cloud
fooLocal = Foo(id: .whole(local: "aaaa", cloud: "bbbb"))

```

2. If it gets created on cloud first, and delivered to device later, then it will be like:

```swift
/// parse the payload from the cloud
var fooCloud = Foo(id: .cloud("bbbb"))
/// finalized on local device
fooCloud = Foo(id: .whole(local: "aaaa", cloud: "bbbb"))

```

In summary, all `Foo`s will come with partial `ID` and become whole at a later time. So what I want to achieve is, to treat the them equal if they share the same partial ID or the whole ID.

It is easy to implement that logic for the `Equtable` protocol of `ID`:

```swift
public static func == (lhs: Self, rhs: Self) -> Bool {
    switch (lhs, rhs) {

    case (.cloud, .local):
        return false

    case (.local, .cloud):
        return false

    case let (.whole(local: l1, cloud: l2), .whole(local: r1, cloud: r2)):
        return l1 == r1 && l2 == r2

    case let (.whole(local: l, cloud: _), .local(r)):
        return l == r

    case let (.whole(local: _, cloud: l), .cloud(r)):
        return l == r

    case let (.local(l), .local(r)):
        return l == r

    case let (.local(l), .whole(local: r, cloud: _)):
        return l == r

    case let (.cloud(l), .cloud(r)):
        return l == r

    case let (.cloud(l), .whole(local: _, cloud: r)):
        return l == r
    }
}

```

### But I am not entirely sure how to implement the `Hashable` of `ID`, so that the `hashValue` could be the same if the `Foo` instances share the same `id` partially or wholly.

Basically, I want this to happen:

```swift
var fooIDSet: Set<Foo.ID> = []

let fooIDLocal = Foo.ID.local("aaaa")
let fooIDCloud = Foo.ID.cloud("bbbb")
let fooIDWhole = Foo.ID.whole(local: "aaaa", cloud: "bbbb")

print(fooIDSet.insert(fooIDWhole).inserted) /// <= This prints `true`
print(fooIDSet.insert(fooIDLocal).inserted) /// <= This prints `false`
print(fooIDSet.insert(fooIDCloud).inserted) /// <= This prints `false`

```

All helps are appreciated!

---

<div class="post-metadata">

**Author:** ![pyrtsa](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/pyrtsa/32/740_2.png) [@pyrtsa](https://forums.swift.org/u/pyrtsa)\
**Post date:** [March 31, 2025, 7:19pm UTC](https://forums.swift.org/t/how-to-implement-the-same-hashability-for-different-values/78974/2 "2025-03-31T19:19:54Z")

</div>

> [@ylorn](#):
>
> ### But I am not entirely sure how to implement the `Hashable` of `ID`, so that the `hashValue` could be the same if the `Foo` instances share the same `id` partially or wholly.

I'm afraid you can't. And part of the reason is you haven't really defined a reasonable equivalence relation either: it breaks the transitivity law where `a == b` and `b == c` should hold if and only if also `a == c`!

Consider the example of:

```swift
let a = Foo.ID.local("1")
let b = Foo.ID.whole(local: "1", cloud: "2")
let c = Foo.ID.cloud("2")

```

According to your definition of `==`, `a == b` and `b == c`, but not `a == c`.

Now, if you find a way to implement `==` so that all the three laws ([including also reflexivity and symmetry](https://developer.apple.com/documentation/swift/equatable#Conforming-to-the-Equatable-Protocol)) hold, your challenge will be to implement `Hashable` in such a way that for all `a` and `b` which compare as equal, their hash values are also equal.

The trouble is if you somehow define a `.local(x)` to be equal to another `.whole(x, y)`, then you must necessarily ignore `y` in the hash computation; OTOH if `.whole(x, y)` shall compare equal to `.cloud(y)`, then you must also ignore `x` in the hash computation. That leaves you with very little wiggle room for the hash algorithm, with practically all ID values having the same hash value.

---

<div class="post-metadata">

**Author:** ![AlexanderM](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/alexanderm/32/9781_2.png) [@AlexanderM](https://forums.swift.org/u/AlexanderM)\
**Post date:** [March 31, 2025, 8:37pm UTC](https://forums.swift.org/t/how-to-implement-the-same-hashability-for-different-values/78974/3 "2025-03-31T20:37:31Z")

</div>

I think you need some process that redefines IDs locally from whatever was pulled from the cloud, so ultimately there's only ever 1 relevant ID per object

---

<div class="post-metadata">

**Author:** ![tera](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tera/32/38201_2.png) [@tera](https://forums.swift.org/u/tera)\
**Post date:** [March 31, 2025, 9:42pm UTC](https://forums.swift.org/t/how-to-implement-the-same-hashability-for-different-values/78974/4 "2025-03-31T21:42:08Z")

</div>

While you can do it, you probably don't want doing it:

```
public func hash(into hasher: inout Hasher) {}

```

this way the hash/EQ invariant is not broken.

* * *

Note that the end result (of redefining EQ/hash for your ID values) could be surprising. Example:

```swift
struct S: Hashable {
    var value: Int
    public static func == (lhs: S, rhs: S) -> Bool {
        lhs.value >= 10 && rhs.value >= 10 ? true : lhs.value == rhs.value
    }
    func hash(into hasher: inout Hasher) {
        let v = value >= 10 ? 10 : value
        hasher.combine(v)
    }
}

let a1 = Set((0 ... 10).map(S.init))
let b1 = Set([S(value: 20)])
let c1 = a1.union(b1)
print("first:", c1) // contains S with 10

// same but in a different order
let a2 = Set([S(value: 20)])
let b2 = Set((0 ... 10).map(S.init))
let c2 = a2.union(b2)
print("second:", c2) // contains S with 20

precondition(c1 == c2)

```
