Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
92: HashMap<K,V> Members In Depth
I've spent a lot of time reviewing PRs where developers treat a Rust HashMap like a JavaScript object or a Python dictionary. They write code that works, but it's fighting the borrow checker or doing way more work than necessary. One of the most common patterns I see is the "check-then-act" loop.
use std::collections::HashMap;
fn track_word_counts(text: &str) {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
// This is the "anti-pattern"
if counts.contains_key(word) {
let count = counts.get(word).unwrap();
counts.insert(word, count + 1);
} else {
counts.insert(word, 1);
}
}
println!("{:?}", counts);
}
The Triple-Lookup Performance Trap
At first glance, the code above looks fine. It's logical: check if the key exists, get the value, and update it. But from a software engineering perspective, this is inefficient. You're asking the HashMap to hash the key and search the buckets three separate times: once for contains_key, once for get, and once for insert.
In a tight loop processing a massive log file, this is a huge waste of CPU cycles. Moreover, that .unwrap() is a smell; we already checked if the key existed, but the compiler doesn't know that, so we're forced to use a risky call to convince it that the value is there.
Using the Entry API for Atomic Updates
This is where the Entry enum comes in. Instead of checking and then acting, you use the entry() method to "reserve" a spot in the map. This returns an Entry enum that represents either an occupied slot or a vacant one. You perform the lookup once and then decide what to do with the result.
use std::collections::HashMap;
fn track_word_counts(text: &str) {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
// One lookup, one update. Clean and fast.
counts.entry(word)
.and_modify(|count| *count += 1)
.or_insert(1);
}
println!("{:?}", counts);
}
I love the Entry API because it's functionally expressive. and_modify only runs if the key exists, and or_insert only runs if it doesn't. The borrow checker is happy because we're working with a mutable reference to the value directly inside the map, rather than pulling a value out and trying to push a new one back in.
Pruning Data with Retain and Drain
Beyond just inserting and getting, you'll often need to clean up your maps. If you find yourself iterating over a map and collecting keys into a Vec just so you can loop over that Vec and call remove(), stop. You're doing too much work.
The retain method is your best friend here. It allows you to pass a closure that returns a boolean; if the closure returns false, the element is removed. It's essentially a filter for your map that happens in-place.
let mut scores = HashMap::new();
scores.insert("Alice", 50);
scores.insert("Bob", 20);
scores.insert("Charlie", 80);
// Remove everyone who scored below 40
scores.retain(|name, score| *score >= 40);
Similarly, if you need to move all the elements out of a map while keeping the map's allocated capacity for future use, use drain(). Unlike clear(), which just wipes the data, drain() returns an iterator over the removed key-value pairs, allowing you to process them as they leave the map.
📋 Practical Task
Build a Multi-User Session Cache
You are building a simple session manager for a web server. You need to implement a SessionCache that tracks how many requests each user has made and allows for the cleanup of "inactive" or "low-usage" sessions.
Requirements:
- Create a
HashMapwhere the key is aString(Username) and the value is anu32(Request Count). - Implement a method
record_request(user: &str)that uses theEntryAPI to increment the request count or initialize it to 1 if the user is new. - Implement a method
cleanup_inactive(min_requests: u32)that usesretainto remove all users who have made fewer thanmin_requests. - Implement a method
export_all_sessions()that usesdrainto return all remaining sessions as aVecof tuples, leaving the map empty but preserving its capacity.
Test your implementation with this scenario: 1. Record requests for "Alice" (3 times), "Bob" (1 time), and "Charlie" (5 times). 2. Cleanup sessions with fewer than 2 requests (Bob should be removed). 3. Export all sessions and verify that only Alice and Charlie remain.
There are no comments for now.