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
94: HashSet and BTreeSet
A few years ago, I was helping a teammate optimize a log analysis tool that flagged unique IP addresses during a suspected DDoS attack. The original code was simple: it pushed every incoming IP into a Vec and then called contains() to see if they'd seen that IP before. It worked perfectly in the staging environment with a few thousand logs. But the moment it hit production with millions of entries, the tool practically ground to a halt. The time complexity was O(n) for every single check; as the list grew, the program spent all its time scanning the same list over and over.
The fix took about thirty seconds. We swapped the Vec for a HashSet, and the execution time dropped from minutes to milliseconds. That's the power of choosing the right data structure for the job. In Rust, when you care about uniqueness and fast lookups, you want a Set.
Fast Lookups with HashSet
A HashSet is essentially a HashMap where you only care about the keys, not the values. It uses a hashing function to map your data to a specific bucket in memory, allowing it to check if an item exists in constant time—O(1)—regardless of whether you have ten items or ten million.
I usually reach for HashSet by default because it's incredibly efficient. However, there is one trade-off: it doesn't preserve the order of insertion. If you iterate over a HashSet, the elements will come out in a seemingly random order. Here is how you'll typically use it in a real project:
use std::collections::HashSet;
fn main() {
let mut visitor_ips = HashSet::new();
// These will all be added
visitor_ips.insert("192.168.1.1");
visitor_ips.insert("10.0.0.1");
// This is a duplicate; insert returns false and does nothing
let was_new = visitor_ips.insert("192.168.1.1");
println!("Was the IP new? {}", was_new); // false
println!("Unique visitors: {}", visitor_ips.len());
}
Ordering Data with BTreeSet
Now, there are times when that "random" order of a HashSet is a dealbreaker. Maybe you're building a leaderboard, or a system that needs to output a sorted list of unique usernames. This is where BTreeSet comes in. Instead of hashing, it uses a B-Tree internally to keep your elements sorted.
The trade-off here is performance. A BTreeSet has O(log n) time complexity for insertions and lookups. While that's slower than the O(1) of a HashSet, it's still vastly superior to a Vec for uniqueness checks. More importantly, it gives you the ability to perform range queries—like finding all elements greater than X but less than Y—which is impossible with a hash-based set.
use std::collections::BTreeSet;
fn main() {
let mut scores = BTreeSet::new();
scores.insert(50);
scores.insert(10);
scores.insert(100);
scores.insert(50); // Duplicate, ignored
// BTreeSet iterates in sorted order automatically
for score in &scores {
println!("{}", score);
}
// Output: 10, 50, 100
}
If you're undecided, ask yourself: "Do I need this list to be sorted?" If the answer is no, stick with HashSet. If you need sorting or range-based lookups, BTreeSet is your tool.
📋 Practical Task
Build a Unique Ingredient Auditor
You are building a nutrition app that merges ingredients from multiple recipe versions. Sometimes different versions of a recipe list the same ingredient twice (e.g., "Salt" listed in the prep and the simmer stages). You need to create a tool that takes a list of ingredients and returns a sorted, unique list of everything required for the shopping list.
Requirements:
- Create a function
audit_ingredients(ingredients: Vec<String>) -> Vec<String>. - The function should remove all duplicate ingredients.
- The final list must be returned in alphabetical order.
- Use the most appropriate
Setimplementation to handle both uniqueness and sorting efficiently.
fn main() {
let recipe_v1 = vec!["Flour".to_string(), "Sugar".to_string(), "Butter".to_string()];
let recipe_v2 = vec!["Sugar".to_string(), "Eggs".to_string(), "Flour".to_string()];
let mut all_ingredients = recipe_v1;
all_ingredients.extend(recipe_v2);
let shopping_list = audit_ingredients(all_ingredients);
println!("{:?}", shopping_list);
// Expected: ["Butter", "Eggs", "Flour", "Sugar"]
}There are no comments for now.