Swift의 Hashable

Swift의 Hashable

1. Hashable 이란?

Swift에서 Hashable값을 해시(hash)할 수 있도록 보장하는 프로토콜입니다. Set이나 Dictionary 같은 컬렉션에서 요소나 키를 빠르게 검색하기 위해 내부적으로 해시 테이블을 사용하는데, 여기서 필요한 것이 바로 Hashable입니다.

struct User: Hashable {
    let id: Int
    let name: String
}

위의 User는 자동으로 Hashable을 따릅니다. (id와 name 모두 Hashable이므로)

2. Hashable의 조건

Hashable은 Equatable을 상속하므로, 두 가지 규칙이 반드시 지켜져야 합니다.

  1. a == b이면 반드시 a.hashValue == b.hashValue여야 한다.
    • 같다고 판정되는 값은 항상 같은 해시를 가져야 합니다.
  2. 반대로, a.hashValue == b.hashValue라고 해서 반드시 a == b일 필요는 없다.
    • 해시 충돌이 허용되기 때문입니다. 다른 값이 같은 해시를 가질 수도 있습니다.

이 원칙을 어기면 컬렉션의 동작이 꼬이게 됩니다.

그런데 공식 문서에서는

3. ==이 true인데 hashValue가 다르면 생기는 일

가장 위험한 경우는 동등한 값인데 서로 다른 해시값을 가지는 경우입니다. 이건 Hashable의 규칙을 어긴 구현으로, Set이나 Dictionary 같은 컬렉션에서 심각한 문제를 일으킵니다.

예시를 보겠습니다.

struct Foo: Hashable {
    let id: Int
    let name: String

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

    static func ==(lhs: Foo, rhs: Foo) -> Bool {
        lhs.name == rhs.name // id는 무시
    }
}

let a = Foo(id: 1, name: "A")
let b = Foo(id: 2, name: "A")

print(a == b) // true
print(a.hashValue == b.hashValue) // false

let set: Set = [a, b]
print(set.count) // 2 (둘은 같은 값인데도 다른 원소로 취급됨)