HashMaps in Rust
A HashMap<K, V> stores key-value pairs and delivers O(1) average-case lookup, insertion, and deletion. It lives in std::collections and is one of the most frequently reached-for collections after Vec.
Importing HashMap
Unlike Vec, HashMap is not in the prelude, so you must bring it into scope.
use std::collections::HashMap;
Creating a HashMap
use std::collections::HashMap;
// Empty map — types are inferred from the first insert
let mut scores: HashMap<String, i32> = HashMap::new();
// Pre-allocate for a known number of entries
let mut map: HashMap<&str, u32> = HashMap::with_capacity(50);
// Collect from an iterator of (key, value) tuples
let pairs = vec![("one", 1), ("two", 2), ("three", 3)];
let map: HashMap<&str, i32> = pairs.into_iter().collect();
println!("{:?}", map); // {"one": 1, "two": 2, "three": 3}keys.into_iter().zip(values).collect().Inserting Values
use std::collections::HashMap;
let mut scores: HashMap<String, i32> = HashMap::new();
// insert returns the old value if the key already existed
scores.insert("Alice".to_string(), 100);
scores.insert("Bob".to_string(), 85);
let old = scores.insert("Alice".to_string(), 120);
println!("{:?}", old); // Some(100) — Alice's previous score
println!("{:?}", scores);
// {"Alice": 120, "Bob": 85}Accessing Values
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("x", 42);
map.insert("y", 99);
// .get() — safe, returns Option<&V>
if let Some(val) = map.get("x") {
println!("x = {}", val); // x = 42
}
// Index operator — panics if key is missing
let val = map["y"];
println!("y = {}", val); // y = 99
// .get() with a default via unwrap_or
let z = map.get("z").copied().unwrap_or(0);
println!("z = {}", z); // z = 0Checking for Keys
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("Rust", 2010);
// Check if a key exists
println!("{}", map.contains_key("Rust")); // true
println!("{}", map.contains_key("Go")); // false
// Size and emptiness
println!("len={}", map.len()); // 1
println!("empty={}", map.is_empty()); // falseRemoving Values
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("a", 1);
map.insert("b", 2);
// remove returns Option<V> — the old value if the key existed
let removed = map.remove("a");
println!("{:?}", removed); // Some(1)
let missing = map.remove("z");
println!("{:?}", missing); // NoneIterating
HashMap does not guarantee any particular iteration order. If you need ordered output, sort the keys first or use a BTreeMap.
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);
// Iterate over key-value pairs (borrowed)
for (key, val) in &map {
println!("{}: {}", key, val);
}
// Keys only
for key in map.keys() {
println!("key: {}", key);
}
// Values only
for val in map.values() {
println!("val: {}", val);
}
// Mutable values
for val in map.values_mut() {
*val *= 10;
}
// Sorted output — collect keys, sort, then look up
let mut keys: Vec<&&str> = map.keys().collect();
keys.sort();
for k in keys {
println!("{}: {}", k, map[k]);
}The Entry API
The Entry API is HashMap's most powerful feature. It lets you inspect and update a key in a single lookup, avoiding the double-lookup that .contains_key() + .insert() would require.
use std::collections::HashMap;
let mut map: HashMap<&str, i32> = HashMap::new();
// Insert a default only if the key is absent
map.entry("score").or_insert(0);
println!("{:?}", map); // {"score": 0}
// Do nothing if the key already exists
map.entry("score").or_insert(999);
println!("{:?}", map); // {"score": 0} — unchanged
// Modify the existing value via the returned mutable reference
*map.entry("score").or_insert(0) += 10;
println!("{:?}", map); // {"score": 10}Word Count — Classic Entry API Example
Counting word frequencies is the canonical demonstration of the Entry API.
use std::collections::HashMap;
let text = "hello world hello rust world hello";
let mut counts: HashMap<&str, u32> = HashMap::new();
for word in text.split_whitespace() {
let count = counts.entry(word).or_insert(0);
*count += 1;
}
// Sort by frequency descending for display
let mut pairs: Vec<(&&str, &u32)> = counts.iter().collect();
pairs.sort_by(|a, b| b.1.cmp(a.1));
for (word, count) in pairs {
println!("{}: {}", word, count);
}
// hello: 3
// world: 2
// rust: 1or_insert_with — Lazy Initialisation
Use or_insert_with when computing the default value is expensive and you only want to pay the cost if the key is actually missing.
use std::collections::HashMap;
fn expensive_default() -> Vec<i32> {
// Imagine loading from a file or making a network call
vec![1, 2, 3]
}
let mut cache: HashMap<&str, Vec<i32>> = HashMap::new();
// The closure only runs if "results" is not already in the map
cache.entry("results").or_insert_with(expensive_default);
// or_insert_with_key gives you access to the key inside the closure
cache.entry("other").or_insert_with_key(|k| {
println!("computing default for key: {}", k);
vec![0]
});Updating Values
use std::collections::HashMap;
let mut map: HashMap<&str, i32> = HashMap::new();
map.insert("hp", 100);
// Pattern 1 — entry API (preferred)
*map.entry("hp").or_insert(0) -= 20;
// Pattern 2 — get_mut for complex updates
if let Some(hp) = map.get_mut("hp") {
*hp = (*hp).max(0); // clamp to 0
}
// Pattern 3 — replace entirely
map.insert("hp", 50);
println!("{:?}", map); // {"hp": 50}Ownership with HashMap
When you insert values that implement Copy (like integers), copies are stored. For types like String, ownership is moved into the map.
use std::collections::HashMap;
let key = String::from("name");
let value = String::from("Alice");
let mut map = HashMap::new();
map.insert(key, value);
// key and value are now moved — this would not compile:
// println!("{}", key);
// Use references if you need to keep the original
let key_ref = "age";
let value_ref = 30;
let mut map2: HashMap<&str, i32> = HashMap::new();
map2.insert(key_ref, value_ref); // i32 is Copy, so value_ref still usable
println!("age still accessible: {}", value_ref);Custom Hash Functions
Rust's default hasher is SipHash 1-3, which is resistant to HashDoS attacks but not the fastest option. For performance-critical applications where the input is trusted, you can swap in a faster hasher via the BuildHasher trait.
// Add to Cargo.toml: ahash = "0.8"
use std::collections::HashMap;
use ahash::AHasher;
use std::hash::BuildHasherDefault;
type FastMap<K, V> = HashMap<K, V, BuildHasherDefault<AHasher>>;
let mut map: FastMap<&str, i32> = FastMap::default();
map.insert("fast", 1);BTreeMap — Sorted Alternative
Use BTreeMap<K, V> when you need keys in sorted order. The API mirrors HashMap but all operations are O(log n).
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert("banana", 3);
map.insert("apple", 1);
map.insert("cherry", 2);
// Iterates in alphabetical key order — guaranteed
for (k, v) in &map {
println!("{}: {}", k, v);
}
// apple: 1
// banana: 3
// cherry: 2
// Range queries are unique to BTreeMap
use std::ops::Bound::Included;
for (k, v) in map.range("apple"..="banana") {
println!("{}: {}", k, v);
}Common Patterns
Grouping Items
use std::collections::HashMap;
let words = vec!["apple", "avocado", "banana", "blueberry", "cherry"];
let mut by_letter: HashMap<char, Vec<&str>> = HashMap::new();
for word in &words {
let first = word.chars().next().unwrap();
by_letter.entry(first).or_insert_with(Vec::new).push(word);
}
for (letter, group) in &by_letter {
println!("{}: {:?}", letter, group);
}Memoisation / Caching
use std::collections::HashMap;
fn fibonacci(n: u64, cache: &mut HashMap<u64, u64>) -> u64 {
if n <= 1 { return n; }
if let Some(&result) = cache.get(&n) {
return result;
}
let result = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);
cache.insert(n, result);
result
}
let mut cache = HashMap::new();
println!("{}", fibonacci(50, &mut cache)); // 12586269025Quick Reference
Operation | Method | Returns |
|---|---|---|
Insert | .insert(k, v) | Option<V> (old value) |
Get | .get(&k) | Option<&V> |
Get mutable | .get_mut(&k) | Option<&mut V> |
Index | map[&k] | &V — panics if missing |
Remove | .remove(&k) | Option<V> |
Contains | .contains_key(&k) | bool |
Entry or insert | .entry(k).or_insert(v) | &mut V |
Entry or closure | .entry(k).or_insert_with(f) | &mut V |
Iterate pairs | for (k, v) in &map | (&K, &V) |
Keys | .keys() | Iterator<Item=&K> |
Values | .values() | Iterator<Item=&V> |
Length | .len() | usize |
Clear | .clear() | () |