Chapter 27: Rust HashMap
Rust HashMap (HashMap<K, V>), which is the most commonly used key-value store in Rust programs.
When you need to associate keys with values (like city → population, username → score, word → count), you almost always reach for HashMap. It’s fast for lookups, insertions, and deletions (average O(1) time), and it’s completely memory-safe thanks to Rust’s ownership and borrowing rules.
Let me explain Rust HashMap like your personal teacher sitting next to you with code open: slowly, step by step, with lots of runnable examples, everyday Hyderabad analogies (phone contacts, biryani orders, traffic signals), comparisons to other languages, and all the important patterns & gotchas.
1. What is HashMap<K, V>? (The Core Idea)
HashMap<K, V> = hash table that stores key-value pairs.
- Keys (K) must implement Eq + Hash (most types do: String, &str, i32, bool, custom structs…)
- Values (V) can be anything
- Keys are unique — inserting same key twice overwrites the old value
- Unordered (iteration order is unpredictable — use BTreeMap if you need sorted keys)
- Stored on the heap, grows automatically
- Very fast average-case lookups, inserts, removes
Analogy: Think of your phone contacts app.
- Key = person’s name (“Rahul”, “Priya”)
- Value = phone number / details
- You can quickly find Priya’s number (fast lookup)
- You can’t have two “Priya” entries with different numbers (unique keys)
- Adding a new friend is fast, even when you have thousands of contacts
2. Creating a HashMap – All Common Ways
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
use std::collections::HashMap; fn main() { // Way 1: empty + insert let mut population: HashMap<String, u64> = HashMap::new(); // Way 2: with capacity (performance hint - avoids reallocations) let mut scores = HashMap::with_capacity(100); // Way 3: from iterator (very idiomatic) let cities = vec![ ("Hyderabad", 10_000_000), ("Warangal", 800_000), ("Nizamabad", 500_000), ]; let city_pop: HashMap<_, _> = cities.into_iter().collect(); println!("City populations: {:?}", city_pop); } |
- Always mut if you plan to insert/remove
- Keys are owned by the map (usually String, not &str)
3. Basic Operations – Insert, Get, Remove
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
fn main() { let mut population = HashMap::new(); // Insert / overwrite population.insert("Hyderabad".to_string(), 10_000_000); population.insert("Hyderabad".to_string(), 10_500_000); // overwrites // Get (returns Option<&V>) if let Some(pop) = population.get("Hyderabad") { println!("Population: {} million", pop / 1_000_000); } // Safe get with default let nizamabad_pop = population.get("Nizamabad").copied().unwrap_or(0); println!("Nizamabad: {}", nizamabad_pop); // Remove if let Some(old_pop) = population.remove("Warangal") { println!("Removed Warangal (was {} people)", old_pop); } println!("Remaining: {:?}", population); } |
- insert(key, value) → returns old value if key existed (Option<V>)
- get(key) → Option<&V> (immutable borrow)
- remove(key) → Option<V> (takes ownership of value)
4. The Entry API – Most Idiomatic Way (Very Important!)
The .entry() method is the recommended way for “get or insert” patterns.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
fn main() { let mut word_count = HashMap::new(); let text = "biryani biryani haleem biryani irani chai"; for word in text.split_whitespace() { // Classic but less idiomatic // let count = word_count.entry(word.to_string()).or_insert(0); // *count += 1; // Better: one-liner with or_insert_with *word_count.entry(word.to_string()).or_insert(0) += 1; } println!("Word counts: {:?}", word_count); } |
Other useful entry patterns:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
// Insert only if not present word_count.entry("pizza".to_string()).or_insert(1); // Modify only if present if let std::collections::hash_map::Entry::Occupied(mut e) = word_count.entry("biryani".to_string()) { *e.get_mut() += 10; // extra spicy biryani count! } |
5. Iterating Over HashMap
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
fn main() { let mut scores = HashMap::new(); scores.insert("Webliance".to_string(), 95); scores.insert("Friend1".to_string(), 82); scores.insert("TopPlayer".to_string(), 98); // Iterate by reference (most common) for (name, score) in &scores { println!("{} → {} points", name, score); } // Mutate while iterating (careful!) for (_, score) in scores.iter_mut() { *score += 5; // bonus points } // Consume the map (moves out) for (name, score) in scores { println!("Consumed: {} had {} points", name, score); } } |
- &map → (&K, &V) pairs
- &mut map → (&K, &mut V)
- map → (K, V) (consumes map)
6. Common Gotchas & Best Practices (2026 Style)
- Prefer String keys over &str in the map itself (owns the data)
- Use &str for lookup: map.get(“Hyderabad”) works via Deref coercion
- Don’t use HashMap when you need sorted keys → use BTreeMap
- For very small maps → consider [(K,V); N] array + linear search
- Capacity: use .with_capacity(n) or .reserve(n) when you know approximate size
- Avoid cloning keys unnecessarily — use .entry() patterns
Practice Challenge for Today
Create cargo new rust_hashmap and try:
- HashMap<String, u32> city → population (add 5 Telangana cities)
- Use .entry() to count words in a sentence about Hyderabad food
- Function that takes &HashMap<String, i32> and prints top 3 scores
- Add 100 random key-value pairs → check len() vs capacity()
Want next?
- BTreeMap vs HashMap (when order matters)?
- HashMap with custom keys (need Hash + Eq)?
- Iterators on HashMap (.map, .filter, .collect)?
- Or structs containing HashMap + ownership/borrowing?
Just tell me — your Rust teacher is right here with you! 🦀🚀
