Closures

Rust · Practical · 12 min

Closures power iterators, callbacks, threads and async code, and their capture rules follow directly from ownership.

A closure is an anonymous function that can capture variables from the scope where it is defined: let add = |a, b| a + b; or with a block body |x: i32| -> i32 { x * factor }. Parameter and return types are usually inferred from the first use.

Closures capture in the least demanding way their body allows: by shared reference if they only read a variable, by mutable reference if they modify it, or by value if they move it out. Borrowing rules apply to those captures, so a closure holding &mut count blocks other uses of count while it is alive.

The capture mode decides which Fn trait the closure implements:

  • FnOnce: can be called at least once; it may consume captured values. Every closure implements it.
  • FnMut: can be called repeatedly and may mutate captures.
  • Fn: can be called repeatedly through a shared reference; it only reads captures.

APIs take the most permissive bound that works for them: Option::map takes FnOnce because it calls once, Iterator::for_each takes FnMut, and things called concurrently or many times through & take Fn.

The `move` keyword forces captures by value, even if the body only reads them. You need it when the closure outlives the current scope: returning a closure, spawning a thread, or passing it into async tasks. Moving a Copy value copies it; to move a clone and keep the original, clone into a new variable first.

To accept a closure, use generics (fn apply<F: Fn(i32) -> i32>(f: F)), and to return one, -> impl Fn(i32) -> i32. When you need to store closures of different types together, box them: Vec<Box<dyn Fn(i32) -> i32>>. Plain fn items also implement all three traits, so you can pass a function name wherever a closure is expected.

Example

fn apply_twice<F: FnMut()>(mut f: F) { f(); f(); }

fn make_multiplier(k: i32) -> impl Fn(i32) -> i32 {
    move |x| x * k // `move`: the closure owns its copy of k
}

fn main() {
    let mut count = 0;
    apply_twice(|| count += 1);        // captures &mut count -> FnMut
    println!("count = {count}");       // count = 2

    let triple = make_multiplier(3);
    println!("{}", triple(7));         // 21

    let name = String::from("ferris");
    let consume = move || name;        // moves name out when called -> FnOnce only
    let back = consume();
    // consume();                      // error: closure cannot be called twice
    println!("{back}");

    let ops: Vec<Box<dyn Fn(i32) -> i32>> = vec![Box::new(|x| x + 1), Box::new(triple)];
    println!("{:?}", ops.iter().map(|f| f(10)).collect::<Vec<_>>()); // [11, 30]
}

Remember

  • Closures capture by reference, mutable reference or value, as needed by the body
  • Fn ⊂ FnMut ⊂ FnOnce: callers ask for the weakest bound they can
  • Use move when the closure must outlive the scope that created it

Watch out for

  • Forgetting move on a closure passed to thread::spawn and getting a 'may outlive borrowed value' error.

Check yourself

  1. A closure pushes into a captured `Vec`. Which is the most specific trait it implements?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: doc.rust-lang.org/book/ch13-01-closures.html