Iterator chains are the idiomatic way to process collections in Rust and compile to code as fast as hand-written loops.
An iterator is anything implementing trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }. Each call to next yields Some(item) until it returns None. Item is an associated type: each iterator has exactly one item type.
Collections give you three iterators: iter() yields &T, iter_mut() yields &mut T, and into_iter() yields owned T, consuming the collection. A for loop calls into_iter() on whatever you give it, which is why for x in &v yields references.
Adapters turn an iterator into another iterator: map, filter, filter_map, enumerate, zip, chain, take, skip, take_while, flat_map, rev, peekable, windows on slices. Adapters are lazy: nothing happens until a consumer pulls items. A map with no consumer does nothing (and the compiler warns).
Consumers drive the iteration: collect, sum, count, min/max, max_by_key, fold, any, all, find, position, for_each, last.
collect is generic over its output: it can build a Vec, String, HashMap, HashSet, and even a Result<Vec<T>, E> from an iterator of Results, stopping at the first error. Tell it what you want with a type annotation or turbofish: collect::<Vec<_>>().
Thanks to inlining, iterator chains usually compile to the same machine code as a manual loop, and they often avoid bounds checks. Prefer them for transforming data; use a plain for loop when the body has complex control flow or side effects.
To make your own type iterable, implement next; all adapters come with the trait. Implement IntoIterator for your collection so it works in for loops.
Example
struct Countdown(u32);
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.0 == 0 { return None; }
self.0 -= 1;
Some(self.0 + 1)
}
}
fn main() {
let words = ["apple", "bob", "kayak", "rust"];
let palindromes: Vec<String> = words.iter()
.filter(|w| w.chars().eq(w.chars().rev()))
.map(|w| w.to_uppercase())
.collect();
println!("{palindromes:?}"); // ["BOB", "KAYAK"]
let nums: Result<Vec<i32>, _> = "1,2,x".split(',').map(str::parse::<i32>).collect();
println!("{}", nums.is_err()); // true: stops at the first error
let total: u32 = Countdown(4).zip(1..).map(|(a, b)| a * b).sum();
println!("{total}"); // 4*1 + 3*2 + 2*3 + 1*4 = 20
println!("{:?}", words.iter().max_by_key(|w| w.len())); // Some("kayak"): last maximum wins
}Remember
- Implement
nextand you get every adapter for free - Adapters are lazy; consumers like
collect,sumandfordrive them collectcan build anyFromIteratortype, includingResult<Vec<_>, E>
Watch out for
- Calling
.map(...)for side effects without a consumer, so nothing runs.