🗃️ HashMaps: Your Magic Label Maker
Imagine you have a huge box of toys. Instead of digging through to find your favorite car, you put labels on little drawers. Write “Red Car” on one drawer, open it, and boom—there’s your car!
That’s exactly what a HashMap does in Rust. It’s like having magic drawers with labels. You give it a name (called a “key”), and it gives you back the thing (called a “value”).
🏗️ Creating HashMaps
The Toy Box Analogy
Think of creating a HashMap like setting up your labeled drawer system.
use std::collections::HashMap;
// Create an empty drawer system
let mut scores: HashMap<String, i32> =
HashMap::new();
What’s happening here?
HashMap::new()= “Give me an empty box with drawers”String= The type of label (text)i32= The type of thing inside (numbers)mut= “I want to add stuff later”
Quick Way: From Pairs
Got a list of things already? Use collect():
let teams = vec![
("Blue", 10),
("Red", 20),
];
let scores: HashMap<&str, i32> =
teams.into_iter().collect();
It’s like saying: “Here’s my list—put each pair into its own labeled drawer!”
graph TD A["🎯 Your Data"] --> B["vec! of pairs"] B --> C[".into_iter#40;#41;"] C --> D[".collect#40;#41;"] D --> E["📦 HashMap Ready!"]
📖 Accessing HashMap Values
Opening the Right Drawer
You have labeled drawers. How do you peek inside?
let mut snacks = HashMap::new();
snacks.insert("cookies", 5);
snacks.insert("apples", 3);
// Method 1: Using get() - SAFE
let cookie_count = snacks.get("cookies");
// Returns Some(5)
// Method 2: Direct access with []
let apple_count = snacks["apples"];
// Returns 3 directly
🛡️ Why get() is Safer
When you ask for something that doesn’t exist:
| Method | What Happens |
|---|---|
get("pizza") |
Returns None (politely says “not found”) |
snacks["pizza"] |
💥 PANIC! Program crashes |
Always prefer get() unless you’re 100% sure the key exists!
// Safe pattern
match snacks.get("pizza") {
Some(count) => println!("Got {} pizzas", count),
None => println!("No pizza 😢"),
}
// Or use if-let
if let Some(count) = snacks.get("cookies") {
println!("Yay! {} cookies!", count);
}
✏️ Updating HashMaps
Three Ways to Change Things
Imagine your drawer already has something inside. What do you want to do?
1️⃣ Overwrite Everything
let mut ages = HashMap::new();
ages.insert("Alice", 25);
ages.insert("Alice", 30); // Replaces 25!
// Alice is now 30
Like erasing the old label and writing a new number.
2️⃣ Only Insert if Empty
ages.insert("Bob", 20);
ages.entry("Bob").or_insert(99);
// Bob stays 20 (drawer wasn't empty)
ages.entry("Carol").or_insert(35);
// Carol becomes 35 (drawer WAS empty)
Like saying: “Only put this in if the drawer is empty.”
3️⃣ Update Based on Old Value
let text = "hello world hello rust";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let count = word_count
.entry(word)
.or_insert(0);
*count += 1;
}
// hello: 2, world: 1, rust: 1
This is counting! For each word:
- If new → start at 0
- Add 1 to whatever’s there
graph TD A["word: &#39;hello&#39;"] --> B{Exists?} B -->|No| C["Create with 0"] B -->|Yes| D["Get current value"] C --> E["Add 1"] D --> E E --> F["Store back"]
🚪 Entry API: The Smart Door
The Entry API is like a smart door that knows if someone’s home.
What is entry()?
scores.entry("Blue")
This returns an Entry—a special helper that says:
- “Here’s the door to ‘Blue’”
- “I’ll tell you if anyone’s inside”
- “I’ll help you decide what to do”
The Power Moves
| Method | What It Does | When to Use |
|---|---|---|
or_insert(val) |
Put val if empty |
Default values |
or_insert_with(fn) |
Run function if empty | Expensive defaults |
or_default() |
Use type’s default | Quick zeros/empty |
and_modify(fn) |
Change if exists | Update existing |
Real Example: Game Scores
let mut scores = HashMap::new();
// Player joins → start at 0
scores.entry("Alice").or_insert(0);
// Player scores → add points
scores.entry("Alice")
.and_modify(|s| *s += 100)
.or_insert(100);
🧙♂️ Pro Pattern: Modify OR Insert
scores
.entry("Bob")
.and_modify(|s| *s += 50) // If exists: add 50
.or_insert(50); // If not: start at 50
This single line handles BOTH cases beautifully!
📚 Standard Collections Overview
HashMaps are just ONE tool in Rust’s collection toolbox!
The Big Three
graph TD A["Rust Collections"] --> B["Vec<T>"] A --> C["HashMap<K,V>"] A --> D["HashSet<T>"] B --> B1["📋 Ordered List"] C --> C1["🏷️ Label → Value"] D --> D1["🎯 Unique Items Only"]
| Collection | Think of it as… | Use When… |
|---|---|---|
Vec<T> |
A numbered list | Order matters |
HashMap<K,V> |
Labeled drawers | Need fast lookup by name |
HashSet<T> |
A bag of unique marbles | No duplicates allowed |
Quick Comparison
// Vec: Ordered, can repeat
let nums: Vec<i32> = vec![1, 2, 2, 3];
// [1, 2, 2, 3] - keeps duplicates!
// HashSet: Unordered, no repeats
let unique: HashSet<i32> =
vec![1, 2, 2, 3].into_iter().collect();
// {1, 2, 3} - 2 appears once!
// HashMap: Key-value pairs
let ages: HashMap<&str, i32> =
vec![("Alice", 30)].into_iter().collect();
// {"Alice": 30}
Other Collections (Bonus!)
| Collection | Superpower |
|---|---|
VecDeque |
Add/remove from both ends fast |
BinaryHeap |
Always gives you the biggest item first |
BTreeMap |
Like HashMap but keeps keys sorted |
BTreeSet |
Like HashSet but keeps items sorted |
🎯 Quick Decision Guide
“Which collection should I use?”
graph TD Q1{Need key-value pairs?} Q1 -->|Yes| Q2{Keys must be sorted?} Q1 -->|No| Q3{Need unique items?} Q2 -->|Yes| BT["BTreeMap"] Q2 -->|No| HM["HashMap ⭐"] Q3 -->|Yes| Q4{Items must be sorted?} Q3 -->|No| V["Vec"] Q4 -->|Yes| BS["BTreeSet"] Q4 -->|No| HS["HashSet"]
💡 Key Takeaways
- HashMap = Magic labeled drawers 🗃️
- Use
get()for safe access (returnsOption) - Entry API is your friend for smart updates
or_insert→ default valuesand_modify→ change existing- Pick the right collection for your needs!
You’ve got this! HashMaps are everywhere in real programs—now you know how to use them like a pro! 🚀
