Closures and Iterators · about 35 min

Fn, FnMut and FnOnce

Why this matters

To accept a closure as a parameter, store one in a struct, or return one, you need to name its type. Every closure has its own unique, unnameable type, so Rust describes closures by what they can do instead, with three traits: Fn, FnMut and FnOnce. They correspond exactly to the three ways of capturing from the last lesson, and choosing the right bound makes your function accept as many closures as possible.

The three traits

They form a hierarchy: every Fn is also FnMut, and every FnMut is also FnOnce. The compiler decides which traits a closure implements from its body, not from whether you wrote move (move only decides how values get into the closure).

Taking closures as parameters

Use a generic parameter with the appropriate bound:

fn call_twice<F: Fn() -> String>(f: F) -> String {
    format!("{} {}", f(), f())
}

fn call_many<F: FnMut()>(mut f: F, n: u32) {
    for _ in 0..n {
        f();
    }
}

fn call_once<F: FnOnce() -> Vec<i32>>(f: F) -> usize {
    let v = f();
    v.len()
}

fn main() {
    let word = String::from("echo");
    println!("{}", call_twice(|| word.clone()));

    let mut log = Vec::new();
    call_many(|| log.push("tick"), 3);
    println!("{:?}", log);

    let data = vec![1, 2, 3];
    println!("{}", call_once(move || data));
}
echo echo
["tick", "tick", "tick"]
3

How to choose the bound for your parameter: ask for as little as you need.

The standard library follows this: Option::unwrap_or_else takes FnOnce (called at most once), Vec::retain takes FnMut, and thread::spawn takes FnOnce plus Send + 'static.

Passing a closure that does not satisfy the bound is a clear error:

fn call_twice<F: Fn()>(f: F) {
    f();
    f();
}

fn main() {
    let mut count = 0;
    call_twice(|| count += 1);
    println!("{count}");
}
error[E0594]: cannot assign to `count`, as it is a captured variable in a `Fn` closure
 --> main.rs:8:19
  |
1 | fn call_twice<F: Fn()>(f: F) {
  |                           - change this to accept `FnMut` instead of `Fn`
...
8 |     call_twice(|| count += 1);
  |     ---------- -- ^^^^^^^^^^ cannot assign
  |     |          |
  |     |          in this closure
  |     expects `Fn` instead of `FnMut`

Ordinary functions work too

A named function can be passed wherever a closure is expected, since functions implement all three traits:

fn double(x: i32) -> i32 {
    x * 2
}

fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}

fn main() {
    println!("{}", apply(double, 21));
    let words = ["a", "bb", "ccc"];
    let lens: Vec<usize> = words.iter().map(|w| w.len()).collect();
    let upper: Vec<String> = words.iter().map(|w| w.to_uppercase()).collect();
    let strs: Vec<String> = [1, 2].iter().map(ToString::to_string).collect();
    println!("{:?} {:?} {:?}", lens, upper, strs);
}
42
[1, 2, 3] ["A", "BB", "CCC"] ["1", "2"]

There is also the plain function pointer type fn(i32) -> i32 (lowercase fn), which can hold named functions and closures that capture nothing. It is a single concrete type, handy in tables, but cannot hold capturing closures.

Storing closures: generics or Box<dyn Fn>

A struct can hold a closure either through a type parameter (fast, one type per struct instance) or a trait object (any closure, chosen at run time):

struct Button<F: Fn() -> String> {
    label: String,
    on_click: F,
}

struct Menu {
    items: Vec<(String, Box<dyn Fn() -> String>)>,
}

fn main() {
    let b = Button { label: String::from("OK"), on_click: || String::from("confirmed") };
    println!("{}: {}", b.label, (b.on_click)());

    let user = String::from("ada");
    let menu = Menu {
        items: vec![
            (String::from("greet"), Box::new(move || format!("hello {user}"))),
            (String::from("time"), Box::new(|| String::from("noon"))),
        ],
    };
    for (name, action) in &menu.items {
        println!("{name} -> {}", action());
    }
}
OK: confirmed
greet -> hello ada
time -> noon

Note (b.on_click)(): the parentheses tell Rust to call the field, not a method named on_click. The Menu needs Box<dyn Fn> because its closures have different types. Same trade-off as module 10: generics for speed and single types, dyn for mixing.

Returning closures

Return impl Fn... for a single closure type, Box<dyn Fn...> when different branches return different closures:

fn adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

fn op(name: &str) -> Box<dyn Fn(i32, i32) -> i32> {
    match name {
        "add" => Box::new(|a, b| a + b),
        "mul" => Box::new(|a, b| a * b),
        _ => Box::new(|_, _| 0),
    }
}

fn main() {
    let add5 = adder(5);
    println!("{} {}", add5(1), op("mul")(6, 7));
}
6 42

Common mistakes

Recap

Going further: read the docs for std::ops::FnOnce, FnMut and Fn, and look at the signatures of Option::map, Vec::sort_by and std::thread::spawn to see which trait each requires.

Exercises

Exercise 1 · write code

Higher-order functions

Write two generic functions:

The given main prints:

[2, 4, 6]
[11, 12, 13]
called 4 times

File: main.rs

Starting point

fn main() {
    let v = [1, 2, 3];
    println!("{:?}", apply_all(&v, |x| x * 2));
    let offset = 10;
    println!("{:?}", apply_all(&v, |x| x + offset));
    let mut calls = 0;
    repeat_call(4, || calls += 1);
    println!("called {calls} times");
}

Example

expected output

[2, 4, 6]
[11, 12, 13]
called 4 times
Hint 1

Call a closure parameter like a function: f(v).

Hint 2

An FnMut parameter must be declared mut f: F because calling it may change its captured state.

Reference solution
fn apply_all<F: Fn(i64) -> i64>(values: &[i64], f: F) -> Vec<i64> {
    let mut out = Vec::with_capacity(values.len());
    for &v in values {
        out.push(f(v));
    }
    out
}

fn repeat_call<F: FnMut()>(n: usize, mut f: F) {
    for _ in 0..n {
        f();
    }
}

fn main() {
    let v = [1, 2, 3];
    println!("{:?}", apply_all(&v, |x| x * 2));
    let offset = 10;
    println!("{:?}", apply_all(&v, |x| x + offset));
    let mut calls = 0;
    repeat_call(4, || calls += 1);
    println!("called {calls} times");
}

Exercise 2 · write code

A table of operations

Build a calculator that stores its operations in a Vec<(&str, Box<dyn Fn(f64, f64) -> f64>)> with entries for +, -, *, / and max. The given main reads lines A OP B and prints the result with 2 decimals, or unknown OP for an operator that is not in the table:

5.00
1.50
unknown %
8.00

for the lines 2 + 3, 3 / 2, 1 % 2, 8 max 3. Write fn ops() -> Vec<(&'static str, Box<dyn Fn(f64, f64) -> f64>)>.

File: main.rs

Starting point

use std::io;

fn main() {
    let table = ops();
    for line in io::stdin().lines() {
        let line = line.unwrap();
        let parts: Vec<&str> = line.split_whitespace().collect();
        let a: f64 = parts[0].parse().unwrap();
        let b: f64 = parts[2].parse().unwrap();
        match table.iter().find(|(name, _)| *name == parts[1]) {
            Some((_, f)) => println!("{:.2}", f(a, b)),
            None => println!("unknown {}", parts[1]),
        }
    }
}

Example

input

2 + 3
3 / 2
1 % 2
8 max 3

expected output

5.00
1.50
unknown %
8.00
Hint 1

Each closure has its own type, so a Vec of them needs a common type: Box<dyn Fn(f64, f64) -> f64>.

Hint 2

("+", Box::new(|a, b| a + b)); for max, annotate the parameters (|a: f64, b: f64| a.max(b)) so the method call can be resolved.

Reference solution
use std::io;

fn ops() -> Vec<(&'static str, Box<dyn Fn(f64, f64) -> f64>)> {
    vec![
        ("+", Box::new(|a, b| a + b)),
        ("-", Box::new(|a, b| a - b)),
        ("*", Box::new(|a, b| a * b)),
        ("/", Box::new(|a, b| a / b)),
        ("max", Box::new(|a: f64, b: f64| a.max(b))),
    ]
}

fn main() {
    let table = ops();
    for line in io::stdin().lines() {
        let line = line.unwrap();
        let parts: Vec<&str> = line.split_whitespace().collect();
        let a: f64 = parts[0].parse().unwrap();
        let b: f64 = parts[2].parse().unwrap();
        match table.iter().find(|(name, _)| *name == parts[1]) {
            Some((_, f)) => println!("{:.2}", f(a, b)),
            None => println!("unknown {}", parts[1]),
        }
    }
}

Exercise 3 · quiz

Which trait does it implement?

let name = String::from("Ada");
let consume = move || name;

Which is the most general trait consume implements?

Answer
  • FnOnce only

The body returns name, moving the String out of the closure. After one call the closure no longer has a name to give, so it can be called only once: it implements FnOnce but not FnMut or Fn. A closure that only reads its captures is Fn; one that modifies them is FnMut; one that moves out of them is FnOnce only.