Iterators, closures and transformations
Iterators, closures and transformations
A teacher wants adjusted marks only for students who have met the target. An iterator supplies items one at a time. Adapters such as filter and map describe a transformation, while a consuming operation such as collect actually drives it. A closure is an inline function-like value that can capture surrounding data.
What you’ll learn
- Trace filter and map through each original mark.
- Recognize lazy adapters, captured values and consuming iteration.
Iterators, closures and transformations in action
Input: 60, 75, 90
The pipeline drops 60, keeps 75 and 90, then adds the captured bonus of five.
Input: 60, 75, 90Filter: 75, 90
Collect produces a new vector containing 80 and 95.
Filter: 75, 90Map +5: 80, 95
The source vector remains unchanged because the iteration borrowed it and copied its integer elements.
Map +5: 80, 95
Iterators, closures and transformations explained
iter() borrows elements; copied() turns these Copy integer references into integer values. Filter keeps values whose predicate is true, map transforms each remaining value, and collect builds the result. Most iterator adapters are lazy: describing the pipeline does not itself perform every operation.
Work through the Rust program
Save the complete source as main.rs in the Rust environment. No standard input is needed.
Rust 2024 · complete program
fn main() {
let marks = vec![60, 75, 90];
let bonus = 5;
let adjusted: Vec<_> = marks.iter().copied()
.filter(|score| *score >= 75)
.map(|score| score + bonus)
.collect();
println!("Adjusted: {adjusted:?}");
println!("Original: {marks:?}");
}Expected output
Adjusted: [80, 95]
Original: [60, 75, 90]Follow the execution
The pipeline drops 60, keeps 75 and 90, then adds the captured bonus of five. Collect produces a new vector containing 80 and 95. The source vector remains unchanged because the iteration borrowed it and copied its integer elements.
Try the complete program in the Rust compiler. For tests, Cargo commands and local files, use the specified local environment.
Choosing how a closure captures data
A closure that only reads the bonus can borrow it. A closure that changes captured state needs mutable calling access, and one that consumes a captured value can be callable only once. move changes capture to ownership or copying; it does not mean the closure must run on another thread. A closure’s Fn, FnMut and FnOnce capabilities follow what its body does with captures.
For a simple total, a loop may be easier for a beginner to debug. Use a pipeline when its sequence of filtering and transformation communicates the policy clearly, not because it is automatically faster.
Try a small variation
Your task: What result would a bonus of zero produce?
Rust 2024 · complete program
fn main() {
let marks = vec![60, 75, 90];
let bonus = 5;
let adjusted: Vec<_> = marks.iter().copied()
.filter(|score| *score >= 75)
.map(|score| score + bonus)
.collect();
println!("Adjusted: {adjusted:?}");
println!("Original: {marks:?}");
}Worked answer
It would produce [75, 90]. Filtering still removes 60; changing the mapping bonus does not remove the filter operation.
Mistakes and limits to recognize
Closures can capture by shared borrow, mutable borrow or ownership. Fn, FnMut and FnOnce describe calling requirements; they do not mean every closure has the same captures. A pipeline may consume its source when built from into_iter rather than iter.
Check your understanding
Take the idea into your next program
It would produce [75, 90]. Filtering still removes 60; changing the mapping bonus does not remove the filter operation. Closures can capture by shared borrow, mutable borrow or ownership.
Further reading: the official Rust Book on iterators, closures and transformations.
Reaching the recap and completing the quizzes are separate milestones.