Per utilizzare strutture personalizzate nelle collezioni tipo HashMap o HashSet, è necessario implementare i trait Eq e Hash (spesso anche PartialEq). L'implementazione di questi trait determina come gli oggetti vengono confrontati tra loro e come viene calcolato il loro hash.
Importante: se due oggetti sono uguali (a == b), i loro valori hash devono coincidere (hash(a) == hash(b)). In caso contrario, i contenitori che dipendono dagli hash non funzioneranno correttamente (sarà impossibile trovare o rimuovere un elemento).
Esempio di implementazione:
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); } }
È possibile implementare Hash per una struttura in cui alcuni campi partecipano al confronto di uguaglianza (Eq/PartialEq), mentre alcuni solo all'hashing?
Risposta:
No, questo porterà a una violazione dell'invariante: se due elementi sono considerati uguali, il loro hash deve essere identico. Tutti i campi coinvolti in PartialEq/Eq devono essere considerati anche in Hash. Ignorare i "campi non confrontabili" è sicuro solo se non partecipano alla logica di confronto.
impl Hash for MyType { fn hash<H: Hasher>(&self, state: &mut H) { self.x.hash(state); // se solo self.x è usato anche in Eq } }
Storia
In uno grande progetto server, una struttura per la chiave era utilizzata in
HashMap, ma nell'hashing è stato dimenticato un campo che partecipava aPartialEq. Di conseguenza, l'impossibilità di trovare la chiave necessaria ha portato a una "fuga" di utenti dalla cache: gli elementi erano considerati uguali, anche se erano diversi.
Storia
In una libreria open source per oggetti geometrici, in
HashedEqveniva utilizzato il confronto con punto flottante (f64). Di conseguenza, a causa delle peculiarità del confronto NaN e -0.0/+0.0, i contenitori standard funzionavano con errori, a volte non trovando un oggetto esistente nella collezione.
Storia
In un progetto fintech, durante il refactoring è stata utilizzata la derivazione automatica
#[derive(Hash, Eq, PartialEq)]per una struttura con campi privati, modificando l'ordine della loro dichiarazione. Di conseguenza, l'hash finale è cambiato, interrompendo il funzionamento della caching e causando perdite di dati dopo il riavvio.