Um benutzerdefinierte Strukturen in Collections wie HashMap oder HashSet zu verwenden, müssen die Traits Eq und Hash (häufig auch PartialEq) implementiert werden. Die Implementation dieser Traits definiert, wie Objekte miteinander verglichen werden und wie ihr Hash-Wert berechnet wird.
Wichtig: Wenn zwei Objekte gleich sind (a == b), dann müssen ihre Hash-Werte übereinstimmen (hash(a) == hash(b)). Andernfalls werden Container, die von Hashs abhängen, nicht korrekt funktionieren (es wird unmöglich sein, ein Element zu finden oder zu entfernen).
Beispielimplementierung:
use std::hash::{Hash, Hasher}; #[derive(Eq, PartialEq)] struct Punkt { x: i32, y: i32, } impl Hash for Punkt { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); self.y.hash(state); } }
Kann man Hash für eine Struktur implementieren, bei der einige Felder an der Gleichheitsprüfung (Eq/PartialEq) teilnehmen und andere nur am Hashing?
Antwort:
Nein, das würde zu einer Verletzung des Invariants führen: Wenn zwei Elemente als gleich angesehen werden, müssen ihre Hashes gleich sein. Alle Felder, die an PartialEq/Eq beteiligt sind, sollten auch in Hash berücksichtigt werden. "Nicht vergleichbare" Felder zu ignorieren ist nur sicher, wenn sie nicht an der Vergleichslogik teilnehmen.
impl Hash für MeinTyp { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); // wenn nur self.x auch in Eq verwendet wird } }
Geschichte
In einem großen Serverprojekt wurde eine Struktur für den Schlüssel in
HashMapverwendet, aber bei der Hash-Berechnung wurde ein Feld vergessen, das anPartialEqbeteiligt war. Infolgedessen führte die Unfähigkeit, den erforderlichen Schlüssel zu finden, zu einer "Auslassung" von Benutzern aus dem Cache: Elemente wurden als identisch angesehen, obwohl sie unterschiedlich waren.
Geschichte
In einer Open-Source-Bibliothek für geometrische Objekte wurde in
HashundEqein Vergleich mit Gleitkommazahlen (f64) verwendet. Infolgedessen arbeiteten die Standardcontainer aufgrund der Besonderheiten des Vergleichs von NaN und -0.0/+0.0 fehlerhaft, manchmal fanden sie ein bestehendes Objekt in der Collection nicht.
Geschichte
In einem Fintech-Projekt wurde bei der Refaktorisierung die Auto-Derivation
#[derive(Hash, Eq, PartialEq)]für eine Struktur mit privaten Feldern verwendet, wobei die Reihenfolge ihrer Deklaration geändert wurde. Infolgedessen änderte sich der endgültige Hash, was das Caching-System kaputt machte und Datenverluste nach einem Neustart verursachte.