RustHashMaps

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.

RUST
use std::collections::HashMap;
Creating a HashMap

RUST
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}
Tip
The .collect() approach is handy when you already have two parallel Vecs of keys and values — zip them together first: keys.into_iter().zip(values).collect().
Inserting Values

RUST
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

RUST
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 = 0
Warning
Using map[&key] when the key does not exist causes a panic. Use .get() whenever there is any chance the key might be absent.
Checking for Keys

RUST
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());  // false
Removing Values

RUST
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); // None
Iterating

HashMap does not guarantee any particular iteration order. If you need ordered output, sort the keys first or use a BTreeMap.

RUST
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.

RUST
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.

RUST
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:  1
or_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.

RUST
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

RUST
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.

RUST
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.

RUST
// 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);
Note
For most applications the default SipHash is the right choice. Only switch hashers after profiling shows the hash function is actually a bottleneck.
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).

RUST
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

RUST
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

RUST
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)); // 12586269025
Quick 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()

()

Success
You now understand how to create, read, update, and delete entries in a Rust HashMap, and how to use the Entry API to write clean, efficient update logic.