🎓 A1Learner
🔍

Rust HashMap Keys

A HashMap key is any type Rust can test for equality and turn into a hash. A hash is a short fingerprint that tells the map where to look first for fast lookup.

Make your own struct a key

The use line from Rust HashMap stays at the top of the file. Derive PartialEq, Eq and Hash, and a struct can serve as a key:

#[derive(PartialEq, Eq, Hash, Debug)]
struct Account { user: String, site: String }
let mut logins: HashMap<Account, &str> = HashMap::new();
logins.insert(Account { user: String::from("ada"), site: String::from("mail") }, "first login");
let probe = Account { user: String::from("ada"), site: String::from("mail") };
println!("lookup = {:?}", logins.get(&probe));

insert tells you what happened

insert hands back an Option: None for a new key, or the old value when it replaced one:

let mut scores: HashMap<String, i32> = HashMap::new();
println!("new key = {:?}", scores.insert(String::from("Blue"), 10));
println!("same key = {:?}", scores.insert(String::from("Blue"), 20));
println!("now = {:?}", scores.get("Blue"));

Search a String map with &str

The keys in the map are Strings, but a plain &str can do the searching — no new String needed:

let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Blue"), 10);
println!("by &str = {:?}", scores.get("Blue"));

remove hands the value back

remove takes a pair out and gives its value back — or None when the key was never there:

let mut scores = HashMap::new();
scores.insert("Blue", 10);
println!("removed = {:?}", scores.remove("Blue"));
println!("again = {:?}", scores.remove("Blue"));

Reserve room with with_capacity

When you know roughly how many pairs are coming, reserve the room up front:

let mut scores: HashMap<String, i32> = HashMap::with_capacity(10);
scores.insert(String::from("Blue"), 10);
println!("pairs = {} room for 10+ = {}", scores.len(), scores.capacity() >= 10);

Example

use std::collections::HashMap;
#[derive(PartialEq, Eq, Hash, Debug)]
struct Account {
    user: String,
    site: String,
}
fn main() {
    let mut logins: HashMap<Account, &str> = HashMap::new();
    let ada = Account { user: String::from("ada"), site: String::from("mail") };
    println!("first insert = {:?}", logins.insert(ada, "first login"));
    let ada_again = Account { user: String::from("ada"), site: String::from("mail") };
    println!("replace insert = {:?}", logins.insert(ada_again, "second login"));
    let lookup = Account { user: String::from("ada"), site: String::from("mail") };
    println!("lookup = {:?}", logins.get(&lookup));
    let mut scores: HashMap<String, i32> = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    println!("Blue by &str = {:?}", scores.get("Blue"));
    println!("removed = {:?}", scores.remove("Blue"));
    println!("after remove = {:?}", scores.get("Blue"));
}

Key points

  • A key needs two promises: keys can be compared for equality, and keys can be hashed.
  • A key type must implement Eq and Hash.
  • Eq says two keys can be tested equal.
  • Eq needs PartialEq first.
  • Hash turns a key into a fingerprint.
  • Lookup uses equality and hash, not identity.
  • f32 and f64 cannot be keys: they do not implement Hash, because floating-point rounding would make lookup unreliable

In this example, Account derives PartialEq, Eq and Hash, so it can be a key. The first insert is new, so it returns None. The second Account is equal to the first. So the insert replaces the value. It returns Some with the old text. A third, fresh Account still finds the entry, because lookup uses equality and hash, not identity. The String-keyed map is then searched with the plain &str "Blue". Then remove hands back the removed value. After that, the last get finds nothing.

Note

A lookup does not need the same key object.

A fresh key can still find the entry.

The fresh key must equal a stored key.

The fresh key must have the same hash.

That is how the fresh Account in the example works.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why can an f64 not be a HashMap key?