Iterators
Rust iterators look like Java streams or Python comprehensions, but they compile down to tight loops with no per-element overhead. The pattern matters because most of the work in a real Rust program is iterating over collections.
A plain loop
let nums = vec![1, 2, 3, 4, 5];
let mut sum = 0;
for n in &nums {
sum += n;
}Works. Reads. Easy to write. But it forces the reader to scan four lines to learn the intent.
The same as a pipeline
let sum: i32 = vec![1, 2, 3, 4, 5].iter().sum();Same machine code, one line.
The shape: source → transform → terminal
let total: i32 = orders
.iter() // source: borrow each item
.filter(|o| o.status == Status::Paid) // keep only paid
.map(|o| o.amount) // project to amount
.sum(); // terminal: collapse to a numberThe pipeline has three phases:
- Source —
.iter(),.iter_mut(),.into_iter(), or any function returningimpl Iterator. - Transform —
.map,.filter,.flat_map,.take,.skip,.enumerate,.zip, etc. - Terminal —
.sum,.collect,.count,.fold,.for_each,.reduce,.find,.any,.all.
Until the terminal runs, no work happens. Iterators are lazy.
.iter() vs .into_iter() vs .iter_mut()
| Method | Yields | Use when |
|---|---|---|
.iter() | &T | Read-only walk. |
.iter_mut() | &mut T | Modify each in place. |
.into_iter() | T (owned) | Consume the collection. |
For most "read only" pipelines, .iter() is right.
Common patterns
Map and collect
let names: Vec<String> = users.iter().map(|u| u.name.clone()).collect();Filter and collect
let active: Vec<&User> = users.iter().filter(|u| u.is_active).collect();Find one
let admin = users.iter().find(|u| u.is_admin);
// admin: Option<&User>Sum, count, average
let total: i32 = orders.iter().map(|o| o.amount).sum();
let count = orders.iter().filter(|o| o.is_paid).count();
let avg = total as f64 / count as f64;Group / aggregate
itertools::group_by for streaming groupings; for hash-based grouping, fold into a HashMap:
use std::collections::HashMap;
let by_status: HashMap<Status, Vec<&Order>> = orders
.iter()
.fold(HashMap::new(), |mut acc, o| {
acc.entry(o.status).or_default().push(o);
acc
});Result-aware pipelines
let parsed: Result<Vec<u16>, _> = inputs
.iter()
.map(|s| s.parse::<u16>())
.collect();When the inner type is Result<T, E>, collecting into Result<Vec<T>, E> propagates the first error. This is one of the most-loved Rust patterns.
Closures
The thing inside .map(|x| ...) is a closure: an anonymous function that captures its environment.
let multiplier = 3;
let scaled: Vec<i32> = nums.iter().map(|n| n * multiplier).collect();Three closure flavors exist (Fn, FnMut, FnOnce), distinguished by what they do with captured variables. For 95% of iterator code, you do not think about the distinction; the compiler picks the right one.
Why this matters beyond style
Iterator pipelines compose. You can extract a part into a helper, parameterize it, return it as impl Iterator, and the consumer gets the same performance.
fn paid_amounts<'a>(orders: &'a [Order]) -> impl Iterator<Item = i32> + 'a {
orders.iter().filter(|o| o.is_paid).map(|o| o.amount)
}
let total: i32 = paid_amounts(&orders).sum();
let count = paid_amounts(&orders).count();impl Iterator says "I return something that implements Iterator; the exact type is hidden." This is how Rust gives you composable laziness without runtime cost.
From the Sail tour
The pattern shows up everywhere in Sail. From sail-catalog::utils:
pub fn quote_names_if_needed<T: AsRef<str>>(names: &[T]) -> String {
names
.iter()
.map(|name| quote_name_if_needed(name.as_ref()))
.collect::<Vec<_>>()
.join(".")
}Five lines, one idea: transform each item, join with dots. Reads as the sentence "quote each name if needed, joined with periods."