Closures and Iterators

Fn, FnMut, and FnOnce, then lazy iterator chains: filter, map, collect, windows, zip, and partition.

Closures and iterators

Traits and lifetimes are in place, so a closure can say how it captures and how long that capture lasts. Learn the closure first. Iterator adapters are the second section, because map and filter take closures. The third section is the adapters you reach for after map and filter feel ordinary.

Closures

A closure is an anonymous function that can use variables from the scope around it. let add_x = |n| n + x captures x. The forms are || expression, |a, b| expression, and |a: i32| -> i32 { a + 1 }.

TraitThe closureHow often
FnOnly reads the captureMany times, including through a shared reference
FnMutAssigns through a captured mut bindingMany times, but not through a shared reference
FnOnceMoves a capture outOnce
xenvironmentFnreadFnMutchangeFnOnceconsume
StatusReading is Fn

A closure that only looks at x implements Fn, and therefore also FnMut and FnOnce. You can call it many times.

Returning a closure is impl Fn(i32) -> i32, the opaque type from traits. Box<dyn Fn()> is the erased form when the concrete closure cannot be named. You already used closures without noticing: unwrap_or_else, and HashMap’s or_insert_with. Iterator adapters are the place you will write them on purpose.

01_closures.rsRust
fn apply_twice(f: impl Fn(i32) -> i32, n: i32) -> i32 {
    f(f(n))
}

fn mul(k: i32) -> impl Fn(i32) -> i32 {
    move |n| n * k
}

fn main() {
    let add_two = |n| n + 2;
    println!("{}", apply_twice(add_two, 3));
    let mut acc = 0;
    let mut add_to_acc = |n| {
        acc += n;
        acc
    };
    println!("{}", add_to_acc(4));
    let owned = String::from("hi");
    let take = move || owned.len();
    println!("{}", take());
    println!("{}", mul(3)(4));
}

Iterators

An iterator produces the next item, or ends. Adapters such as filter and map are lazy: they build a pipeline and do no work until a consumer asks. Consumers include sum, collect, for, find, and fold.

numssourcefilterlazymaplazysumruns it
StatusAdapters wait

iter borrows. iter_mut borrows mutably. into_iter consumes, which you already chose on a Vec. Nothing is filtered until something pulls.

02_iterators.rsRust
fn main() {
    let nums = [1, 2, 3, 4, 5];
    let sum: i32 = nums.iter().filter(|n| *n % 2 == 0).map(|n| *n * 10).sum();
    let labels: Vec<String> = nums.iter().map(|n| format!("n={n}")).collect();
    let found = nums.iter().find(|n| **n == 4).copied();
    println!("{sum} {} {:?}", labels.len(), found);
}

More adapters

AdapterWhat it does
windows(n), chunks(n)Look at adjacent groups of a slice
enumeratePairs an index with the item
zipPairs two iterators and stops at the shorter one
flat_mapTurns each item into an iterator and flattens one level
partitionSplits into two collections with a predicate
nextThe method a custom iterator implements
from_fn, successorsBuild an iterator without a new type

Use these after the pipeline in the previous section is readable. They are the same pull model, not a new kind of loop.

03_iterator_patterns.rsRust
fn main() {
    let nums = [1, 2, 3, 4];
    let mut windows = Vec::new();
    for w in nums.windows(2) {
        windows.push(w[0] + w[1]);
    }
    let names = ["ada", "lin"];
    let scores = [10, 20];
    let pairs: Vec<_> = names.iter().zip(scores).collect();
    let (even, odd): (Vec<_>, Vec<_>) = nums.iter().partition(|n| *n % 2 == 0);
    println!("{:?} {} {}", windows, pairs.len(), even.len() + odd.len());
}