Iterators & Closures

Closures are small anonymous functions that capture their environment; iterators are lazy sequences that transform collections. Chained together they replace most hand-written loops with expressive, zero-cost pipelines.

Closures

A closure is a function without a name that can capture variables from its surroundings. The || holds parameters. Rust assigns one of three traits depending on how the closure captures:

TraitCaptures byTypical use
Fnimmutable referenceread-only, call many times
FnMutmutable referencemay mutate captured state
FnOncevalue (moves)consumes capture, call once
fn main() {
    let prefix = String::from("Hello, ");

    // captures `prefix` by reference (Fn trait)
    let greet = |name: &str| format!("{prefix}{name}");
    println!("{}", greet("Rust"));

    let mut count = 0;
    // captures `count` mutably (FnMut trait)
    let mut inc = || { count += 1; count };
    println!("{} {} {}", inc(), inc(), inc());
}

Iterators

An iterator is a lazy sequence: nothing runs until you consume it. Call .iter() (borrow), .into_iter() (own), or .iter_mut() (mutable) to obtain one, then drive it with a for loop:

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    for n in numbers.iter() {       // borrow each element
        print!("{n} ");
    }
    println!();

    for n in numbers.iter().rev() { // reverse without allocating
        print!("{n} ");
    }
    println!();
}

Combinators

Adapter methods transform one iterator into another lazily. Chain map, filter, and friends, then materialize the result only at the end with collect:

fn main() {
    // square, keep even, sum the whole pipeline
    let total: i32 = (1..=10)
        .filter(|&n| n % 2 == 0)   // keep evens
        .map(|n| n * n)            // square them
        .sum();                    // consume (adds up)

    println!("sum of even squares = {total}");

    let doubled: Vec = (0..5).map(|n| n * 2).collect();
    println!("{doubled:?}");
}

Consuming Adapters

Adapters like fold, count, any, all, and max run the whole iteration and return a single value:

fn main() {
    let words = ["the", "quick", "brown", "fox"];

    let total_len: usize = words.iter().map(|w| w.len()).sum();
    let has_q = words.iter().any(|&w| w.contains('q'));
    let longest = words.iter().max_by_key(|w| w.len());

    println!("total len = {total_len}, has 'q' = {has_q}");
    println!("longest = {:?}", longest);
}
Practice move: rewrite a for loop from the lab examples as a map/filter/collect chain and confirm the output is identical.