Lesson 18 / 25

Iterators and Closures

map, filter, sum, collect.

Lazy, composable, zero-cost

Iterators produce values one at a time; adapters such as map, filter, take and enumerate build lazy pipelines that run only when consumed by sum, count, collect, max_by_key or a for loop. Closures (|o| o.amount > threshold) capture variables from their environment. Iterator chains compile to code as fast as hand-written loops while being clearer. iter() borrows items, into_iter() takes ownership, iter_mut() borrows mutably.

Processing orders with iterator chains, run

I ran this with Rust 1.99.0 (cargo run, edition 2024, standard library only). Paid orders sum to 3,057; names are collected into a Vec; max_by_key finds Meera's order; map applies a 10% discount; a closure captures the threshold; position finds the first unpaid order.

#[derive(Debug)]
struct Order { customer: &'static str, amount: u32, paid: bool }

fn main() {
    let orders = vec![
        Order { customer: "Asha", amount: 1259, paid: true },
        Order { customer: "Ravi", amount: 1200, paid: false },
        Order { customer: "Meera", amount: 1798, paid: true },
    ];
    let paid_total: u32 = orders.iter().filter(|o| o.paid).map(|o| o.amount).sum();
    let names: Vec<&str> = orders.iter().map(|o| o.customer).collect();
    let biggest = orders.iter().max_by_key(|o| o.amount).unwrap();
    let discounted: Vec<u32> = orders.iter().map(|o| o.amount * 90 / 100).collect();
    let threshold = 1250;                                   // closures capture variables
    let above = orders.iter().filter(|o| o.amount > threshold).count();
    println!("paid total {paid_total}");
    println!("{names:?} biggest={}", biggest.customer);
    println!("{discounted:?} above {threshold}: {above}");
    let first_unpaid = orders.iter().position(|o| !o.paid);
    println!("first unpaid index {first_unpaid:?}");
}

Output:

paid total 3057
["Asha", "Ravi", "Meera"] biggest=Meera
[1133, 1080, 1618] above 1250: 2
first unpaid index Some(1)

Prefer iterators over index loops

Iterators avoid bounds checks and off-by-one errors; clippy even suggests them.

Quick check: When does a chain like iter().map(...).filter(...) actually run?

  • When it is consumed, for example by sum, collect or a for loop
  • Immediately when map is called
  • At compile time
  • Never
Answer

When it is consumed, for example by sum, collect or a for loop — Iterators are lazy.