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
93: BTreeMap and Ordered Collections
Think about the difference between a pile of business cards and a physical address book. If you have a pile of cards, you can find a specific person quickly if you have a magic index (that's your HashMap), but if I ask you to "show me everyone whose last name starts with the letter M through P," you're stuck. You'd have to look at every single card in the pile.
An address book, however, is sorted. Because the entries are ordered, you don't just find one person; you can find a range of people. You flip to the 'M' section and stop when you hit 'Q'. That's exactly what BTreeMap does for you in Rust. It keeps your keys in a sorted order, allowing you to perform range queries that would be impossibly slow with a hash map.
When to ditch the HashMap
I'll be honest: 90% of the time, HashMap is your go-to. It's faster for basic insertions and lookups. But the moment you need your data to stay sorted, or you need to find "all keys between X and Y," BTreeMap is the only way to go. It's implemented as a B-Tree, which is specifically designed to be cache-friendly and efficient for these kinds of ordered operations.
use std::collections::BTreeMap;
fn main() {
let mut scores = BTreeMap::new();
// We're inserting scores for players.
// Note that BTreeMap will sort these by the key (the score).
scores.insert(100, "Alice");
scores.insert(50, "Bob");
scores.insert(150, "Charlie");
scores.insert(75, "Dave");
// When we iterate, they come out in order of the key: 50, 75, 100, 150.
for (score, name) in &scores {
println!("{}: {}", score, name);
}
}
The "Ord" Requirement
You'll notice that BTreeMap has a stricter requirement for its keys than HashMap does. While a HashMap key just needs to be Hash + Eq, a BTreeMap key must implement Ord. This is because the map needs to know exactly how to compare two keys to decide which one comes first. If you try to use a custom struct as a key without implementing Ord and PartialOrd, the compiler will stop you right there.
Slicing your data with Range Queries
This is where the real power lies. The .range() method allows you to pull out a slice of the map. You can use standard Rust range syntax (like a..b or a..=b) to define exactly what you're looking for. I find this incredibly useful for things like time-series data or any scenario where you have a "start" and "end" boundary.
use std::collections::BTreeMap;
fn main() {
let mut events = BTreeMap::new();
events.insert(10, "System Boot");
events.insert(20, "Network Up");
events.insert(30, "User Login");
events.insert(40, "File Opened");
events.insert(50, "System Shutdown");
// Let's say we only care about events that happened between time 15 and 35.
println!("Events in window 15..35:");
for (time, desc) in events.range(15..35) {
println!("At time {}: {}", time, desc);
}
// This will print "Network Up" and "User Login".
}
One little tip: .range() returns an iterator. It doesn't clone the data or create a new map; it just gives you a window into the existing one. This makes it extremely efficient, even if your map contains millions of entries.
📋 Practical Task
Build a Versioned Document History
You are building a simple version control system for a text document. Each version is identified by a u32 version number and contains a string representing the document's content at that point in time.
Your goal:
- Create a
BTreeMapwhere the key is the version number (u32) and the value is the content (String). - Insert at least five different versions of the document, but insert them out of order (e.g., insert version 5, then 1, then 3).
- Implement a function
get_version_range(map: &BTreeMap<u32, String>, start: u32, end: u32)that prints all document versions between thestartandendvalues (inclusive).
Testing your logic:
If you insert versions 1 through 5, calling get_version_range with 2 and 4 should print the content for versions 2, 3, and 4 in the correct numerical order, regardless of the order you inserted them into the map.
There are no comments for now.