Introduction
When you need to associate keys with values and look them up efficiently, Rust provides HashMap<K, V>. It offers O(1) average-case lookups and is the go-to data structure for caches, counters, and configuration stores.
Key Concepts
- HashMap<K, V>: A hash table mapping keys of type
Kto values of typeV. Keys must implementEqandHash. - Entry API: A pattern for conditionally inserting or updating values, avoiding redundant lookups.
- Ownership transfer: When you insert owned types like
Stringinto a HashMap, the map takes ownership of them.
Real World Context
HashMaps are used in virtually every non-trivial program: counting word frequencies in a document, caching API responses, grouping records by category, or building an in-memory index. The Entry API is particularly valuable in production code because it handles insert-or-update logic in a single, efficient call.
Deep Dive
You must bring HashMap into scope with a use statement, then create and populate it:
rustuse std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50);
Accessing values uses .get(), which returns Option<&V> because the key may not exist:
rustif let Some(score) = scores.get("Blue") { println!("Blue team: {score}"); }
Ownership is important: inserting a String key moves it into the map. After insertion, the original variable is no longer valid:
rustlet key = String::from("color"); scores.insert(key, 42); // key is now invalid — ownership moved to the map
The Entry API provides three patterns for safe, efficient updates:
rust// Insert only if key is absent scores.entry("Red".to_string()).or_insert(0); // Update based on existing value (word counter) let text = "hello world hello"; let mut counts = HashMap::new(); for word in text.split_whitespace() { let count = counts.entry(word).or_insert(0); *count += 1; }
The or_insert method returns a mutable reference to the value, letting you update it in place.
Common Pitfalls
- Forgetting to import — Unlike
Vec,HashMapis not in the prelude. You must writeuse std::collections::HashMap;or the compiler will report an unresolved type. - Overwriting with insert — Calling
.insert()with an existing key silently replaces the old value. Use the Entry API (.entry().or_insert()) when you want insert-if-absent semantics.
Best Practices
- Use the Entry API for insert-or-update — It performs a single lookup instead of a
.get()followed by.insert(), which would require two lookups and borrowing gymnastics. - Borrow keys for lookups — The
.get()method accepts&QwhereK: Borrow<Q>, so you can look up aStringkey with a&strslice. No need to allocate aStringjust to search.
Summary
HashMap<K, V>provides O(1) average-case key-value lookups.- Keys must implement
Eq + Hash; inserting owned values transfers ownership. .get()returnsOption<&V>, making missing-key handling explicit.- The Entry API (
entry().or_insert()) is the idiomatic way to insert or update. - Always import with
use std::collections::HashMap;.
Code Examples
use std::collections::HashMap;
fn word_count(text: &str) -> HashMap<&str, i32> {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
counts
}
let counts = word_count("hello world hello");
// {"hello": 2, "world": 1}