Hash în Rust

Trait-ul care transformă o valoare într-un hash; necesar (alături de Eq) pentru chei în HashMap și HashSet.

Sintaxă
#[derive(PartialEq, Eq, Hash)]
struct Cheie { ... }

pub trait Hash {
    fn hash<H: Hasher>(&self, state: &mut H);
}

Ce face Hash

HashMap și HashSet găsesc rapid o cheie calculând un număr („hash”) din ea. Tipul cheii trebuie să implementeze Hash și Eq. #[derive(Hash)] combină hash-urile tuturor câmpurilor, în ordine; merge dacă toate câmpurile sunt Hash (nu și f64).

Regula de aur: dacă a == b, atunci hash-urile lor trebuie să fie egale. Dacă implementezi manual PartialEq (de exemplu ignorând literele mari), implementează și Hash manual, folosind exact aceleași informații. Altfel două chei „egale” ajung în locuri diferite din tabel.

Valoarea concretă a hash-ului nu trebuie afișată sau salvată: HashMap folosește o cheie aleatorie la fiecare pornire a programului, pentru protecție împotriva atacurilor.

Parametri

Parametru Descriere
hash(&amp;self, state) Trimite în state datele care identifică valoarea (de obicei cu camp.hash(state)).

Ce returnează

Nimic; datele sunt adăugate în Hasher.

Exemple

Exemplul 1: derive(Hash) pentru o cheie compusă

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
struct Loc {
    rand: u8,
    scaun: u8,
}

fn main() {
    let mut rezervari: HashMap<Loc, &str> = HashMap::new();
    rezervari.insert(Loc { rand: 3, scaun: 7 }, "Ana");
    rezervari.insert(Loc { rand: 1, scaun: 2 }, "Radu");
    rezervari.insert(Loc { rand: 3, scaun: 7 }, "Ioana");   // înlocuiește

    let mut lista: Vec<_> = rezervari.iter().collect();
    lista.sort();
    for (loc, nume) in lista {
        println!("rândul {}, scaunul {}: {nume}", loc.rand, loc.scaun);
    }
}

Rezultat

rândul 1, scaunul 2: Radu
rândul 3, scaunul 7: Ioana

Exemplul 2: Hash manual, în acord cu PartialEq

E-mailurile se compară fără să țină cont de litere mari, deci și hash-ul se calculează din forma cu litere mici. Altfel HashSet ar păstra duplicatele.

use std::collections::HashSet;
use std::hash::{Hash, Hasher};

struct Email(String);

impl PartialEq for Email {
    fn eq(&self, other: &Self) -> bool {
        self.0.eq_ignore_ascii_case(&other.0)
    }
}
impl Eq for Email {}

impl Hash for Email {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.to_ascii_lowercase().hash(state);
    }
}

fn main() {
    let adrese = ["ana@exemplu.ro", "ANA@exemplu.ro", "radu@exemplu.ro", "Ana@Exemplu.ro"];
    let unice: HashSet<Email> = adrese.iter().map(|a| Email(a.to_string())).collect();
    println!("{} adrese, {} unice", adrese.len(), unice.len());
    println!("conține RADU@EXEMPLU.RO: {}", unice.contains(&Email("RADU@EXEMPLU.RO".into())));
}

Rezultat

4 adrese, 2 unice
conține RADU@EXEMPLU.RO: true

Greșeli frecvente

  • Folosești ca cheie un tip fără Hash: the trait bound `Tip: Hash` is not satisfied. Adaugă #[derive(PartialEq, Eq, Hash)].
  • Modifici o cheie după ce a fost pusă în HashMap (prin Cell/RefCell): hash-ul nu mai corespunde, iar cheia nu mai poate fi găsită.

Comenzi înrudite

  • Eq: Trait marker care promite egalitate completă (a == a mereu); cerut de chei în HashMap/HashSet și de Ord.
  • PartialEq: Trait-ul din spatele operatorilor == și !=; derive compară toate câmpurile, implementarea manuală alege criteriul.