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:).