HashMaps
Reviewed & published by Brayan K
Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and the HashMap is its workhorse for mapping keys to values with fast lookups.
Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll create hash maps, insert and look up entries safely, and use the entry API to update values in place.
What You'll Learn in This Lesson
- Importing HashMap from the standard library
- Creating a map and inserting pairs
- Looking up values with .get and Option
- How inserting an existing key overwrites it
- The entry().or_insert() update pattern
- Iterating over key-value pairs
1️⃣ Creating, Inserting, and Looking Up
First bring it into scope with use std::collections::HashMap;. Then create one with HashMap::new(), add pairs with insert, and look up values with .get, which returns an Option so a missing key is handled safely.
// HashMap lives in the standard library; bring it into scope with use.
use std::collections::HashMap;
fn main() {
// A HashMap<K, V> maps keys to values for fast lookup.
let mut scores: HashMap<String, i32> = HashMap::new();
// Insert key-value pairs.
scores.insert(String::from("Ada"), 90);
scores.insert(String::from("Bob"), 85);
// Inserting the same key again OVERWRITES the old value.
scores.insert(String::from("Bob"), 88);
// .get returns Option<&V>: Some(&value) or None.
match scores.get("Ada") {
Some(v) => println!("Ada: {}", v), // -> Ada: 90
None => println!("no Ada"),
}
println!("Bob: {:?}", scores.get("Bob")); // -> Bob: Some(88)
println!("Cat: {:?}", scores.get("Cat")); // -> Cat: None
println!("entries: {}", scores.len()); // -> entries: 2
}Inserting the same key twice overwrote the old value — that's how you update an entry. Because .get returns an Option, looking up a missing key gives None rather than crashing.
2️⃣ The entry API and Iteration
The most elegant HashMap pattern is the entry API. map.entry(key).or_insert(default) inserts a default if the key is missing and hands you a mutable reference to the value either way — perfect for counting. You can also iterate over all pairs with for (k, v) in &map.
use std::collections::HashMap;
fn main() {
let text = "the cat sat on the mat the end";
// Count word frequencies — the classic HashMap use case.
let mut counts: HashMap<&str, i32> = HashMap::new();
for word in text.split_whitespace() {
// entry(...).or_insert(0) gets a mutable ref to the value, inserting
// 0 first if the key is missing. Then *count += 1 increments it.
let count = counts.entry(word).or_insert(0);
*count += 1;
}
// Look up specific words (order of a HashMap is not guaranteed).
println!("'the' appears {} times", counts["the"]); // -> 3 times
println!("'cat' appears {} times", counts["cat"]); // -> 1 times
// Iterate over all pairs.
let mut total = 0;
for (_word, n) in &counts {
total += n;
}
println!("total words: {}", total); // -> total words: 8
}The line *counts.entry(word).or_insert(0) += 1 is the canonical frequency-count idiom. Remember a HashMap's order isn't guaranteed, so we summed a total rather than relying on iteration order.
Your turn. Fill in the blanks marked ___, then run it.
// 🎯 YOUR TURN — finish the HashMap code, then run it.
use std::collections::HashMap;
fn main() {
// 1) Create an empty HashMap from &str to i32.
let mut ages: HashMap<&str, i32> = HashMap::___(); // 👉 the constructor
// 2) Insert two people.
ages.insert("Sam", 30);
ages.___("Lee", 25); // 👉 the method that adds a pair
// 3) Look up Sam's age safely.
match ages.get("Sam") {
Some(a) => println!("Sam is {}", a), // expected: Sam is 30
None => println!("unknown"),
}
// 4) Print the number of entries.
println!("count = {}", ages.len()); // expected: count = 2
}
// ✅ Expected output:
// Sam is 30
// count = 2Mini-Challenge: A Simple Inventory
Track quantities with a HashMap and use the entry API to update. Run it with cargo run.
// 🎯 MINI-CHALLENGE: a simple inventory.
//
// 1. Use std::collections::HashMap.
// 2. Build a HashMap<&str, i32> of fruit -> quantity:
// "apple" 3, "banana" 5, "cherry" 12.
// 3. Add 2 more apples using entry("apple").or_insert(0) then *count += 2.
// 4. Print the apple count (should be 5).
// 5. Compute and print the total quantity across all fruits by iterating.
//
// ✅ Example output:
// Apples: 5
// Total items: 22
fn main() {
// your code here
}Common Errors (and the fix)
- "cannot find type `HashMap` in this scope" — Add use std::collections::HashMap; at the top.
- Panic from map[key] on a missing key — Indexing panics if the key is absent. Use .get(key) for a safe Option.
- "cannot borrow as mutable" — Inserting or using entry requires the map to be let mut.
- Relying on iteration order — A HashMap has no guaranteed order. Sort keys into a Vec or use a BTreeMap if you need order.
Pro Tips
- 💡 Use entry() to count: *map.entry(k).or_insert(0) += 1 is the go-to frequency idiom.
- 💡 Prefer .get() over indexing: it never panics and forces you to handle the missing case.
- 💡 Need sorted keys? Reach for BTreeMap, which keeps keys ordered.
📋 Quick Reference — HashMaps
| Goal | Rust Syntax |
|---|---|
| Import | use std::collections::HashMap; |
| Create | HashMap::new() |
| Insert | map.insert(k, v) |
| Look up | map.get(&k) |
| Update or insert | map.entry(k).or_insert(0) |
| Iterate | for (k, v) in &map { } |
🎉 Lesson Complete!
- ✅ HashMap<K, V> maps keys to values for fast lookup
- ✅ Import it with use std::collections::HashMap;
- ✅ .get returns an Option; re-inserting a key overwrites it
- ✅ entry().or_insert() updates values in place; order isn't guaranteed
Practice quiz
What does a HashMap<K, V> store?
- Only keys
- Only values
- Key-value pairs
- A sorted list
Answer: Key-value pairs. A HashMap maps keys to values for fast lookup by key.
Which import brings HashMap into scope?
- use std::collections::HashMap;
- use std::vec::HashMap;
- use std::map::HashMap;
- It is in the prelude
Answer: use std::collections::HashMap;. HashMap is not in the prelude; use std::collections::HashMap; imports it.
How do you create a new, empty HashMap?
- HashMap::default(0)
- HashMap::empty()
- HashMap![]
- HashMap::new()
Answer: HashMap::new(). HashMap::new() creates an empty map.
What happens when you insert a key that already exists?
- It panics
- The old value is overwritten
- It is ignored
- It returns an error
Answer: The old value is overwritten. Inserting an existing key overwrites the old value.
What does .get(key) return?
- An Option
- The value directly
- A Result
- A reference that may dangle
Answer: An Option. .get returns Option<&V>: Some(&value) or None for a missing key.
What does entry(key).or_insert(0) return?
- A copy of the value
- true or false
- A mutable reference to the value
- The key
Answer: A mutable reference to the value. It inserts a default if missing and returns a mutable reference either way.
Which line is the canonical frequency-count idiom?
- map.insert(k, k+1)
- *map.entry(k).or_insert(0) += 1
- map.get(k).unwrap()
- map.count(k)
Answer: *map.entry(k).or_insert(0) += 1. *map.entry(k).or_insert(0) += 1 counts occurrences in place.
What is the iteration order of a HashMap?
- Sorted by key
- Insertion order
- Reverse order
- Not guaranteed
Answer: Not guaranteed. A HashMap has no guaranteed order; it can even vary between runs.
What happens if you index a HashMap with a missing key?
- Returns None
- Panics
- Returns 0
- Inserts the key
Answer: Panics. Indexing with map[key] panics on a missing key; prefer .get for safety.
Which collection keeps its keys sorted automatically?
- Vec
- HashMap
- BTreeMap
- HashSet
Answer: BTreeMap. BTreeMap keeps keys in sorted order, unlike HashMap.
Continue this course
- Previous: Strings
- Next: Error Handling (the ? operator) — Propagate recoverable errors cleanly with Result and the ? operator
- Quick reference: Rust cheat sheet › Collections
Frequently asked questions
What is a HashMap and when should I use one?
A HashMap<K, V> stores key-value pairs and lets you look up a value by its key very quickly, on average in constant time. Use one whenever you want to associate data — like usernames to scores, words to counts, or product ids to prices — and need fast lookups by key rather than by position. If you need ordered keys instead, the standard library also offers BTreeMap, which keeps keys sorted at the cost of slightly slower lookups.
Why do I have to write use std::collections::HashMap?
Unlike Vec and String, which are in the prelude (automatically available), HashMap is not imported by default. You bring it into scope with 'use std::collections::HashMap;' at the top of your file. This is just Rust keeping the always-available set small; the import is a one-line formality you add whenever you use a HashMap.
What does entry(...).or_insert(...) do?
It is the idiomatic way to update a value that may or may not already exist. entry(key) looks up the key, and or_insert(default) inserts the default value if the key is missing, then returns a mutable reference to the value either way. You then modify it through that reference, for example '*counts.entry(word).or_insert(0) += 1' to count occurrences. This avoids the awkward two-step of checking whether the key exists and then inserting or updating.
Is the order of a HashMap predictable?
No. A HashMap does not guarantee any particular ordering of its keys, and the order can even vary between runs of your program for security reasons. If your code depends on iterating keys in a specific order, do not rely on a HashMap's iteration order. Instead, collect the keys into a Vec and sort it, or use a BTreeMap, which keeps its keys in sorted order automatically.