ユーザー定義の構造体をHashMapやHashSetなどのコレクションで使用するためには、EqおよびHash(しばしばPartialEqも)トレイトを実装する必要があります。これらのトレイトの実装は、オブジェクト間の比較方法とハッシュの計算方法を定義します。
重要な点:二つのオブジェクトが等しい場合(a == b)、それらのハッシュ値は一致しなければなりません(hash(a) == hash(b))。そうでない場合、ハッシュに依存するコンテナは正しく動作せず、要素を見つけたり削除したりできなくなります。
実装の例:
use std::hash::{Hash, Hasher}; #[derive(Eq, PartialEq)] struct Point { x: i32, y: i32, } impl Hash for Point { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); self.y.hash(state); } }
Eq/PartialEqで比較に参加するフィールドがあり、ハッシュ化にのみ参加するフィールドがある構造体に対してHashを実装しても良いでしょうか?
答え:
いいえ、これは不変条件を破ることになります:二つの要素が等しいと見なされる場合、それらのハッシュも同じでなければなりません。PartialEq/Eqに参加するすべてのフィールドは、Hashでも考慮されるべきです。「比較されない」フィールドを無視することは、安全なのはそれらが比較ロジックに関与しない場合だけです。
impl Hash for MyType { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); // もしself.xがEqでも使用されるなら } }
逸話
大規模なサーバープロジェクトで、キー用の構造体が
HashMapで使用されていて、ハッシュ化の際にPartialEqに参加するフィールドを忘れました。その結果、必要なキーを見つけることができず、ユーザーがキャッシュから「漏れ出す」ことになりました:要素は同じと見なされていましたが、実際には異なっていました。
逸話
オープンソースの幾何学オブジェクトライブラリで
HashおよびEqに浮動小数点(f64)の比較を使用した結果、NaNや-0.0/+0.0の比較の特性により、標準コンテナがエラーを起こし、時にはコレクション内の既存のオブジェクトを見つけられませんでした。
逸話
あるフィンテックプロジェクトでリファクタリング時に、プライベートフィールドを持つ構造体に
#[derive(Hash, Eq, PartialEq)]を自動導出で使用し、フィールドの宣言順序を変更しました。その結果、最終的なハッシュが変わり、キャッシュが壊れて、再起動後にデータの損失を招きました。