Search code examples
swifthashable

How to natively hash a struct without randomization?


Is it possible to hash a struct without randomization natively in Swift?

struct Kiwi: Hashable {
    let userId: String
    let selections: Set<Int>
    
    init(userId: String, selections: Set<Int>) {
        self.userId = userId
        self.selections = selections
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(userId)
        hasher.combine(selections)
    }
}

let k1 = Kiwi(userId: "u1", selections: [1])
print(k1.hashValue)

let k2 = Kiwi(userId: "u1", selections: [1])
print(k2.hashValue)

These hash values are not guaranteed to be equal across different points of execution (due to randomization of the hashing function). The purpose of this hash is simply to generate a signature of a struct's values so that they are always the same if the values of the struct are the same, regardless of when it's executed.

For the time being, I've made the struct Equatable and just compare struct instances but would prefer to be able to compare "signatures" (i.e. hashes) instead.


Solution

  • You should make your own protocol, something like:

    protocol Signable {
        /// A signature that's stable across app launches
        var signature: Signature { get }
    }
    

    Equatable/Hashable are standard protocols with very specific semantics (Equality implies substitutability, and Hashability is a heuristic, as a performance optimization of Equatable). They're not catch-all bags of syntax for anything vaguely similar to equality checking or hashing.

    Also beware: the randomly seeded behavior of Hasher is intentional. It protects you against hash-flooding DoS attacks. You should be very careful circumventing this, because you'll be exposing yourself to this vector.

    Using signature to compare instances persisted across app launches seems fine. But I would not recommend using that to implement any kind of hashed data structures, which could be attacked to degenerate into linear-time-lookups.