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
58: Iterators and Iterator Adapters in Rust
One of the most powerful parts of Rust is how it handles sequences of data. If you're coming from a C background, you're probably used to for loops with indices. If you're from Python or JavaScript, you know map and filter. Rust's iterators are a bit of a hybrid—they give you that high-level functional feel, but they compile down to machine code that's often just as fast as a manual loop. This is what we call "zero-cost abstractions."
To see this in action, let's build a simple log parser. Imagine we have a list of log entries, and we only care about the unique error codes from the "ERROR" level messages. We want to ignore "INFO" and "WARN" entirely.
Starting with a raw sequence
First, let's get some dummy data. I'll use a slice of strings here. In a real app, this might be reading from a file, but for this example, we'll keep it in memory.
let logs = vec![
"INFO: System boot complete",
"ERROR: 404 Not Found",
"WARN: High memory usage",
"ERROR: 500 Internal Server Error",
"ERROR: 404 Not Found",
"INFO: User logged in",
"ERROR: 403 Forbidden",
];
Now, I could write a for loop and push results into a new Vec, but that's a lot of boilerplate. Instead, I'll start an iterator chain using .iter().
Filtering out the noise
I only want the errors. The filter adapter is perfect here. It takes a closure that returns a boolean; if it's true, the element stays in the stream.
let errors = logs.iter()
.filter(|line| line.starts_with("ERROR"));
At this point, errors isn't actually a list of strings. It's an Iterator object. It's just a set of instructions waiting to be executed. This is a key concept in Rust: iterator adapters are lazy.
Extracting the error codes
I don't need the whole "ERROR: " prefix; I just want the code (like "404"). I'll use map to transform each string. I'll split the string by the colon and take the second part, trimming the whitespace.
let codes = logs.iter()
.filter(|line| line.starts_with("ERROR"))
.map(|line| line.split(':').nth(1).unwrap_or("Unknown").trim());
The "Where is my data?" moment
Now, here is where I usually trip up when I'm rushing through a project. I'll try to print codes or use it in a loop, and I'll realize... nothing is happening. Or, more likely, I'll try to pass it to a function that expects a Vec and get a compiler error saying I'm passing a Map struct instead.
I forgot that we've only defined how to process the data. We haven't actually told Rust to do it. To trigger the iteration, we need a consuming adapter (or a "terminal operation").
Collecting and counting results
To actually get the data into a collection, I'll use collect(). This is a bit of a magic method because it can turn an iterator into many different things (Vecs, HashSets, etc.), so I usually have to give it a type hint.
Since I only want unique error codes, I'll collect them into a HashSet. This automatically handles the deduplication for me.
use std::collections::HashSet;
let unique_codes: HashSet<&str> = logs.iter()
.filter(|line| line.starts_with("ERROR"))
.map(|line| line.split(':').nth(1).unwrap_or("Unknown").trim())
.collect();
println!("Unique errors found: {:?}", unique_codes);
// Output: Unique errors found: {"404", "500", "403"}
If I just wanted to know how many errors there were without keeping the codes, I could have swapped .collect() for .count(). It's much more efficient because Rust doesn't have to allocate memory for a new collection; it just increments a counter as the items fly through the pipeline.
📋 Practical Task
Exercise: Discounted Luxury Item Total
You are building a checkout system for a high-end store. You have a list of product prices as a Vec<f64>. Your goal is to calculate the total cost of "luxury items" after a discount is applied.
Write a program that does the following using an iterator chain:
- Starts with this dataset:
let prices = vec![12.50, 150.00, 8.00, 200.00, 45.00, 500.00]; - Filters out any item that costs less than
$100.00. - Applies a 15% discount to the remaining luxury items (multiply by
0.85). - Sums the final total using the
.sum()consuming adapter.
Print the final total to the console. Ensure you are using a chain of adapters rather than a for loop.
There are no comments for now.