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
112: Peekable and Windows Iterators
Imagine you're standing at a conveyor belt in a factory. Items are sliding toward you one by one. Usually, your job is simple: take the item, process it, and move on. But sometimes, you can't decide what to do with the current item until you see what's coming next. You lean over the belt and peek at the next object without actually pulling it off. If the next item is a "Special Handle," you process the current item differently. That's exactly what Peekable does.
Now, imagine you're on a train looking out a window. You don't see the entire landscape at once; you see a specific slice of the world. As the train moves forward, that slice shifts. If your window is wide enough to see two trees, you'll see Tree A and Tree B, then Tree B and Tree C, then Tree C and Tree D. This "sliding slice" is the essence of windows().
Looking ahead without committing
In Rust, a standard iterator is a one-way street. Once you call .next(), that element is gone from the iterator. But in many parsing scenarios—like reading a list of numbers where a minus sign might be a subtraction operator or a negative sign—you need to see the next token before you commit to an action.
To do this, we call .peekable() on an iterator. This wraps the iterator in a Peekable struct that adds the .peek() method. I've found this indispensable when writing custom lexers or parsers.
fn main() {
let tokens = vec!["10", "+", "20", "30", "40"];
let mut iter = tokens.into_iter().peekable();
while let Some(token) = iter.next() {
// I want to see if the NEXT token is also a number
// (which might indicate a missing operator)
if let Some(next_token) = iter.peek() {
if next_token.parse::().is_ok() {
println!("Warning: Found two numbers in a row: {} and {}", token, next_token);
}
}
println!("Processed: {}", token);
}
}
Notice that iter.peek() returns an Option<&T>. It gives you a reference to the next item but leaves it sitting right there on the "conveyor belt" for the next call to .next() to actually consume.
The sliding window approach
While Peekable is about looking one step ahead, .windows() is about analyzing overlapping groups. This method is available on slices, not all iterators, because Rust needs to know the data is contiguous in memory to make the windowing efficient.
I often use this when I'm calculating deltas between values—like checking if a stock price is trending upward or finding the biggest jump in a dataset.
fn main() {
let temperatures = [72, 75, 74, 78, 82, 81];
// We want to see every pair of consecutive days
for window in temperatures.windows(2) {
let prev = window[0];
let curr = window[1];
if curr > prev {
println!("Temp rose from {} to {}", prev, curr);
} else {
println!("Temp dropped or stayed same from {} to {}", prev, curr);
}
}
}
Using .windows(2) is infinitely cleaner than the old-school C-style loop where you'd manage an index i and i + 1, constantly worrying about whether you've accidentally stepped past the end of the array. Rust's windows handle the boundary conditions for you; if the slice is shorter than the window size, the iterator simply returns nothing.
Picking the right tool for the job
You might be wondering when to use which. Here is my rule of thumb: Use Peekable when you are consuming a stream and the logic for the current item depends on the next one. Use windows() when you have a fixed collection and you need to analyze overlapping patterns or local trends.
One quick warning: windows() is only for slices. If you have a Vec, you'll call it on the slice version of the vec (which happens automatically in most loops). If you have a generic Iterator, you can't use .windows() directly because the iterator doesn't know if the data is contiguous. In those rare cases, you'd either collect into a Vec first or implement a custom sliding window using a VecDeque.
📋 Practical Task
Implementing a Run-Length Encoder
Your task is to create a simple Run-Length Encoder (RLE). RLE is a basic form of compression where consecutive identical elements are stored as a single data value and a count. For example, the string "AAABBCCCCD" would become "3A2B4C1D".
Write a function encode_rle(input: &str) -> String. Use a Peekable iterator over the characters of the input string to look ahead and count how many times the same character repeats before moving to the next unique character.
Requirements:
- The function must take a
&strand return aString. - You must use
.chars().peekable()to handle the look-ahead logic. - Ensure you handle the final character(s) of the string correctly so they aren't dropped.
fn encode_rle(input: &str) -> String {
// Your code here
}
fn main() {
let test = "AAABBCCCCD";
let result = encode_rle(test);
println!("Encoded: {}", result); // Expected: "3A2B4C1D"
assert_eq!(result, "3A2B4C1D");
}
There are no comments for now.