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 number

The pipeline has three phases:

  1. Source.iter(), .iter_mut(), .into_iter(), or any function returning impl Iterator.
  2. Transform.map, .filter, .flat_map, .take, .skip, .enumerate, .zip, etc.
  3. 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()

MethodYieldsUse when
.iter()&TRead-only walk.
.iter_mut()&mut TModify 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."