Legacy Hashing

ID

swift.legacy_hashing

Severity

info

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

Swift

Tags

code-smell, modernization

Description

Reports a type that declares a manual var hashValue: Int { …​ }. Swift 4.2 (SE-0206) replaced the Hashable.hashValue requirement with func hash(into hasher: inout Hasher); types should override the new entry point so the compiler can combine their hash with the enclosing hash state.

struct Point: Hashable {
    let x: Int; let y: Int
    var hashValue: Int { x ^ y }              // FLAW
}

struct Point: Hashable {
    let x: Int; let y: Int
    func hash(into hasher: inout Hasher) {    // OK
        hasher.combine(x); hasher.combine(y)
    }
}

Rationale

hashValue collapses each value into a single Int, which forces implementers to invent ad-hoc combining functions (XOR, multiply-add, …). Hash quality suffers and collisions become common in nested hashing scenarios. hash(into:) feeds the components directly into the standard Hasher, which mixes them through a high-quality, seeded hash family — the same one the compiler-synthesised conformance uses.

Remediation

Override hash(into:):

struct Point: Hashable {
    let x: Int
    let y: Int
    func hash(into hasher: inout Hasher) {
        hasher.combine(x)
        hasher.combine(y)
    }
    static func == (lhs: Self, rhs: Self) -> Bool { lhs.x == rhs.x && lhs.y == rhs.y }
}

For simple data types, prefer letting the compiler synthesise the conformance entirely — drop both == and hash(into:).