Essential Standard Traits

Hash

The std::hash::Hash trait feeds the bytes and structure of a value into a stateful Hasher so the value can be used as a key in hash-based collections such as HashMap<K, V> and HashSet<T>.

Trait Definition

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

    fn hash_slice<H: Hasher>(data: &[Self], state: &mut H)
    where
        Self: Sized,
    {
        for piece in data {
            piece.hash(state);
        }
    }
}

Hash separates what data is hashed (Hash::hash, implemented by the key type) from how bytes are mixed into a 64-bit integer (Hasher, implemented by the hashing algorithm).

Key Value (e.g., UserId)
   │
   ▼  calls .hash(&mut hasher)
+------------------------+
| H: Hasher (SipHash13)  |  <-- feeds fields sequentially
+------------------------+
   │
   ▼  calls hasher.finish()
u64 Hash Digest (e.g., 0x9f4a_2c81_03b7_e512)

Deriving Hash

Applying #[derive(PartialEq, Eq, Hash)] generates a Hash implementation that feeds each field (and enum discriminant) into the Hasher in declaration order:

use std::collections::HashSet;

#[derive(Debug, PartialEq, Eq, Hash)]
struct Endpoint {
    host: String,
    port: u16,
}

fn main() {
    let mut visited = HashSet::new();
    visited.insert(Endpoint {
        host: String::from("example.com"),
        port: 443,
    });

    let query = Endpoint {
        host: String::from("example.com"),
        port: 443,
    };
    println!("Contains endpoint: {}", visited.contains(&query));
}
Contains endpoint: true

The Eq and Hash Consistency Contract

Every type used as a HashMap or HashSet key must uphold the fundamental invariant:

$$\text{If } a == b\text{, then } \text{hash}(a) == \text{hash}(b)$$

If you implement PartialEq manually to ignore a field (such as a timestamp or cached value), you must also implement Hash manually and omit that exact same field. Deriving Hash while manually implementing PartialEq (Cold/Clippy lint derived_hash_with_manual_eq) breaks HashMap lookups because equal keys produce different hashes:

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

struct Account {
    id: u64,
    display_name: String,
}

impl PartialEq for Account {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for Account {}

impl Hash for Account {
    fn hash<H: Hasher>(&self, state: &mut H) {
        // Must hash ONLY `id` to match `PartialEq`:
        self.id.hash(state);
    }
}

fn main() {
    let mut set = HashSet::new();
    set.insert(Account {
        id: 7,
        display_name: String::from("Alice"),
    });

    let lookup = Account {
        id: 7,
        display_name: String::from("Alice (Renamed)"),
    };
    println!("Found account 7: {}", set.contains(&lookup));
}
Found account 7: true