Transforming and Filtering
Goal of This Episode
Learn the iterator’s most-used transformation and filtering methods, and how chained calls build powerful data pipelines.
Concept
.map(f) — Transforming Each Element
map applies a closure to each element, producing transformed new elements:
fn main() {
let doubled: Vec<i32> = vec![1, 2, 3].iter().map(|x| x * 2).collect();
// [2, 4, 6]
}
Careful! .iter() produces &T, so the closure’s parameter is &i32. If you’d rather not deal with references, pair it with .copied() (coming right up).
.flat_map(f) — map + flatten
flat_map equals map followed by flatten (last episode’s). Each element becomes an iterator via the closure, and everything gets squashed flat:
fn main() {
let words = vec!["abc", "de", "f"];
let chars: Vec<char> = words.iter().flat_map(|s| s.chars()).collect();
// ['a', 'b', 'c', 'd', 'e', 'f']
}
Remember and_then on Option and Result from Episode 7? What flat_map does on iterators is essentially the same — “transform, and since the result is itself a container, flatten.”
.filter(pred) — Filtering Elements
filter keeps only the elements for which the closure returns true:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
// [&2, &4]
}
filter’s closure receives &&T (.iter() already gives &T, and filter borrows once more, making &&T). This trips up beginners regularly, but it becomes second nature with practice.
.copied() and .cloned()
When an iterator produces references (&T) but you want values (T), these two methods copy each element out:
.copied()— requiresT: Copy; copies each&Tinto aT..cloned()— requiresT: Clone; calls.clone()on each&Tto get aT.
fn main() {
let numbers = vec![1, 2, 3];
let owned: Vec<i32> = numbers.iter().copied().collect();
// From &i32 to i32
}
.copied() often pairs with .filter(), dodging the &&T annoyance:
fn main() {
let evens: Vec<i32> = vec![1, 2, 3, 4, 5]
.iter()
.copied()
.filter(|x| x % 2 == 0)
.collect();
// [2, 4] — much cleaner!
}
.rev() — Reversing the Iteration Order
fn main() {
let reversed: Vec<i32> = (1..=5).into_iter().rev().collect();
// [5, 4, 3, 2, 1]
}
.rev() requires the iterator to implement the DoubleEndedIterator trait — meaning it can take elements from both ends. Vec, arrays, and the like support it, but iterators from from_fn don’t (no concept of a “tail end”).
The Power of Chaining
Iterator methods chain freely into data-processing pipelines:
fn main() {
let names = vec!["Andy", "Bob", "Cindy", "David"];
let result: Vec<String> = names
.iter()
.enumerate()
.filter(|(_, name)| name.len() > 3)
.map(|(i, name)| format!("#{}: {}", i + 1, name))
.collect();
}
Each step does one small thing; strung together, they accomplish very complex operations. And because iterators are lazy (next episode), no extra Vecs materialize along the way.
Example Code
fn main() {
let scores = vec![55, 82, 91, 47, 73, 88, 69, 95];
// map — 5 bonus points per score (a curve adjustment)
let adjusted: Vec<i32> = scores.iter().map(|s| s + 5).collect();
println!("After the bonus: {:?}", adjusted);
// flat_map — splitting each word into characters
let words = vec!["Rust", "rocks"];
let all_chars: Vec<char> = words.iter().flat_map(|w| w.chars()).collect();
println!("All the characters: {:?}", all_chars);
// flat_map resembling and_then — keep successful parses, drop failures
let inputs = vec!["42", "not_a_number", "7"];
let parsed: Vec<i32> = inputs.iter().flat_map(|s| s.parse::<i32>()).collect();
println!("Successfully parsed: {:?}", parsed);
// filter — sifting out the passing scores
let passing: Vec<i32> = scores.iter().copied().filter(|&s| s >= 60).collect();
println!("Passing: {:?}", passing);
// copied — from &i32 to i32
let max_score: Option<i32> = scores.iter().copied().max();
println!("\nHighest score: {:?}", max_score);
// cloned — from &String to String
let names = vec![String::from("Alice"), String::from("Bob")];
let cloned_names: Vec<String> = names.iter().cloned().collect();
println!("cloned: {:?}", cloned_names);
println!("The originals remain: {:?}", names);
// rev — reversing
let countdown: Vec<i32> = (1..=5).into_iter().rev().collect();
println!("\nCountdown: {:?}", countdown);
// Chained combinations
println!("\n--- Chained combinations ---");
let long_words: Vec<&str> = vec!["hi", "hello", "hey", "howdy", "greetings"]
.into_iter()
.filter(|w| w.len() >= 4)
.collect();
println!("4+ letters: {:?}", long_words);
// filter + map combined
let words = vec!["hello", "hi", "hey", "howdy", "greetings"];
let long_upper: Vec<String> = words
.iter()
.filter(|w| w.len() >= 4)
.map(|w| w.to_uppercase())
.collect();
println!("\n4+ letters, uppercased: {:?}", long_upper);
}
Recap
.map(f)transforms each element;.filter(pred)drops the non-qualifying ones..flat_map(f)=.map(f)+.flatten()— conceptually likeand_thenonOption/Result..copied()turns each&TintoT(requiresT: Copy);.cloned()is similar but usesClone..rev()reverses the iteration order, requiringDoubleEndedIterator.- These methods chain freely into clear data-processing pipelines.
- Pairing with
.copied()dodgesfilter’s pesky&&Tproblem.