
Swiftでコレクション型(SetやDictionary)を使うときによく出てくるのが Hashableプロトコル です。
「Hashableって何?」「Equatableとどう違うの?」と思う方も多いかもしれません。
この記事では Hashableプロトコルの基本的な意味や使い方、どんな場面で使うのか、関連するプロトコルとの違い までをわかりやすく解説します。
Hashableとは?
HashableはSwift標準ライブラリのプロトコルで、オブジェクトを「ハッシュ値」という数値に変換できることを保証するもの です。
例えば、SetやDictionaryのキーは必ずHashableに準拠していなければいけません。
なぜなら、これらのコレクションは「同じ値かどうか」を高速に判定するために、ハッシュ値を使っているからです。
もしHashableがなかったら、Swiftはそのオブジェクトをどのように一意に区別すればいいかわかりません。
つまり:
- Hashableに準拠 → コレクションの中で重複を避けられる
- Hashableに準拠 → Dictionaryのキーとして使える
という役割を担っています。
具体例:Setで使ってみる
Swift で Set を使う際に重要なのが、要素が Hashable に準拠している必要があるという点です。
Hashable に準拠することで、Swift は要素を一意に識別できるようになり、重複を排除して効率的に管理できます。
次の例では、自作の構造体をHashable に準拠させて、Setの中で使っています。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
struct User: Hashable { var id: Int var name: String } let user1 = User(id: 1, name: "Taro") let user2 = User(id: 2, name: "Hanako") let user3 = User(id: 1, name: "Taro") // user1と同じ let users: Set<User> = [user1, user2, user3] print(users.count) // 出力は2 |
ポイント
- 同じidとnameを持つuser1とuser3は「同じもの」とみなされる
- Setでは自動的に重複が排除され、要素は2つだけ残る
- 内部的には
hash(into:)と==を使って比較している
もしHashableに準拠していなければ、この構造体をSetに入れることはできません。
具体例:Dictionaryのキーとして使う
Swift で Dictionary を使うとき、キーが一意に判別できることがとても重要です。
そのため、キーとなる型は必ず Hashable に準拠していなければなりません。
たとえば、商品ごとに在庫数を管理したい時、Product という構造体を Hashable にしておけば、そのまま Dictionary のキーとして利用できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct Product: Hashable { var id: Int var name: String } let apple = Product(id: 1, name: "Apple") let banana = Product(id: 2, name: "Banana") let stock: [Product: Int] = [ apple: 100, banana: 50 ] print(stock[apple]!) // 出力は100 |
ポイント
ProductがHashableに準拠しているからこそキーとして利用可能- Hashableでなければ「どのProductがどの在庫数に対応しているか」を区別できない
このように Hashable は、Set の重複排除だけでなく、Dictionary のキー管理にも欠かせません。
Swift のコレクション型を効果的に使うための基盤となる概念なのです。
SwiftUIでの例
SwiftUIのListでもHashableはよく使われます。
|
1 2 3 4 5 6 7 8 9 10 |
struct ContentView: View { let fruits = ["Apple", "Banana", "Orange"] var body: some View { List(fruits, id: \.self) { fruit in Text(fruit) } } } |
ポイント解説
-
id: \.selfを指定しているのは、各要素そのものを ID として使うという意味 -
StringはHashableに準拠しているので、文字列の内容を使って一意に判定できる
→"Apple"と"Banana"は異なる要素として区別される -
もし
Hashableでなければ、SwiftUI は「どの要素がどのセルに対応するのか」を識別できないため、Listを正しく描画できない
主要なメソッドと自動実装
Hashable に準拠するときに関わるメソッドは大きく2つあります。
| メソッド | 役割 |
|---|---|
func hash(into hasher: inout Hasher) |
ハッシュ値を計算する |
static func == (lhs: Self, rhs: Self) -> Bool |
Equatable 由来、等価判定 |
Swift では、すべてのストアドプロパティが Hashable に準拠していれば、これらのメソッドは自動的に実装されます。
自動実装の仕組み
Swift では、すべてのストアドプロパティが Hashable に準拠していれば、コンパイラがこれらのメソッドを自動で合成してくれます。
==は「すべてのプロパティが等しいかどうか」を比較するコードが自動生成されるhash(into:)は「すべてのプロパティのハッシュ値を組み合わせる」コードが自動生成される
つまり、開発者が特別なルールを設けない限り、struct や enum に単に Hashable と書くだけで十分です。
なぜ自動生成してくれるのか?
Swift が自動実装を提供するのは、よくある「全プロパティでの比較・ハッシュ化」パターンを簡単に扱えるようにするためです。
もし自動生成がなかったら、次のように毎回自分で書く必要があります:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct User: Hashable { var id: Int var name: String func hash(into hasher: inout Hasher) { hasher.combine(id) hasher.combine(name) } static func == (lhs: User, rhs: User) -> Bool { return lhs.id == rhs.id && lhs.name == rhs.name } } |
これでは定型で書く必要があるコードが多くなってしまいます。
そのため Swift は 「すべてのプロパティを使う標準的な比較とハッシュ化」 を自動的に提供してくれるのです。
Equatable プロトコルとの違い
Hashable を理解する上で、まず比較されるのが Equatable です。
両者の違いをしっかり整理すると、なぜ Hashable に Equatable が含まれているのかがよくわかります。
Equatable とは?
Equatable は「この型のインスタンス同士を == で比較できること」を保証するプロトコルです。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
struct User: Equatable { var id: Int var name: String } let u1 = User(id: 1, name: "Taro") let u2 = User(id: 1, name: "Taro") let u3 = User(id: 2, name: "Hanako") print(u1 == u2) // true print(u1 == u3) // false |
ここで Equatable が保証しているのは:
- 「==」という演算子が使えること
- 2つのインスタンスが「等しい」かどうかを判定するルールを持っていること
「== で比較できるかどうかを保証」というのはつまり、その型に「等しい」「等しくない」を判定する基準を持たせるという意味です。
例えば、id と name がどちらも一致すれば同じであれば、「等しい」、nameは一緒だけど、idが異なる場合は「等しくない」と判定する基準を持たせるということ。
もし Equatable に準拠していなければ、== を使ってその型を比較することがそもそもできません。
Hashable とは?
Hashable は Equatable を含みつつ、さらに 「インスタンスの内容を数値(ハッシュ値)に変換できる」 ことを保証するプロトコルです。
両者の関係
Equatable:インスタンス同士を 等しいかどうか判定できるHashable:インスタンス同士を 等しいかどうか判定できる + ハッシュ値で一意に識別できる
重要なのは、Hashable に準拠すると自動的に Equatable にも準拠する点です。
|
1 2 3 4 5 6 7 8 9 10 |
struct Product: Hashable { var id: Int var name: String } let p1 = Product(id: 1, name: "Apple") let p2 = Product(id: 1, name: "Apple") print(p1 == p2) // true (Equatable の機能) print(Set([p1, p2]).count) // 1 (Hashable の機能) |
活用シーン
Hashable は、データを一意に区別したいときや、効率的に検索・比較したいときに欠かせないプロトコルです。
- Setで重複を避けながらデータを管理したいとき
- Dictionaryのキーに自作の型を使いたいとき
- SwiftUIでListを作るときに
id: \.selfを使う場合(自己Hashableが必要) - データの一意性を保証したいとき
注意点
とても便利な Hashable ですが、使う際にはいくつかの注意点があります。
これを理解しておかないと、思わぬバグや挙動不審の原因になりかねません。
-
ハッシュ値は一意性を完全に保証するものではない
(異なる値が同じハッシュ値を持つ「衝突」が起こる可能性がある。ただし Swift のコレクションは内部で安全に処理している) -
Hashable に準拠すると自動で Equatable にもなる
(つまり==を使った比較も可能になる) -
プロパティに非 Hashable 型が含まれる場合、自動で Hashable にはできない
(その場合は手動でhash(into:)と==を実装する必要がある) -
SwiftUI の
id: \.selfは便利だが注意が必要
(値そのものを ID にしているため、同じ値が複数あると区別できない。また値が変わると別の要素とみなされ、セルが再生成されることもある)
まとめ
今回は Swift の Hashable プロトコルについて解説しました。
- Hashableは「オブジェクトをハッシュ値に変換できること」を保証するプロトコル
- SetやDictionaryでの重複判定やキー利用に必須
- Equatableプロトコルを内包しているため、比較も可能
- SwiftUIのListなど、UI周りでもよく使われる
初心者がまず理解すべきポイントは 「SetとDictionaryで使うための仕組み」 です。
慣れてきたら hash(into:) をカスタマイズして、さらに理解を深めていきましょう!
