🎓 A1Learner
🔍

Rust Fn Traits

The Fn traits are three traits that describe how a closure may be called. Fn covers a closure that only reads its captures, FnMut covers one that changes them, and FnOnce covers one that can run once.

Fn: it only reads

This closure reads factor and changes nothing, so it earns the top rung, Fn:

let factor = 10;
let mul = |n: i32| n * factor;

FnMut: it changes a capture

This closure adds to a captured counter, so it is FnMut. It is callable many times. The closure variable itself must be mut:

let mut count = 0;
let mut bump = || { count += 1; count };
bump();
println!("{}", bump());

FnOnce: it hands a capture out

This closure moves its String out as the answer, so it can only ever be called once:

let word = String::from("hi");
let give = move || word;
println!("{}", give());

One Fn closure, many callers at once

An Fn closure changes nothing. It may be called through shared references. Two threads may call it at the same time, as this scoped-threads example shows. An FnMut closure could not be shared this way. Calling it needs &mut. Two threads can never hold that at once (E0499):

use std::thread;

let unit_price = 4;
let total = |qty: i32| qty * unit_price;
let sums: Vec<i32> = thread::scope(|s| {
    let a = s.spawn(|| total(3));
    let b = s.spawn(|| total(5));
    vec![a.join().unwrap(), b.join().unwrap()]
});
println!("{sums:?}");

FnMut cannot be called through a shared reference

Only Fn can be called through a plain &. Borrowing an FnMut closure and calling it through that borrow is refused:

let mut n = 0;
let mut bump = || n += 1;
let r = &bump;
r(); // error E0596: cannot borrow as mutable

Example

#[derive(Debug)]
struct Rectangle { width: u32, height: u32 }
fn main() {
    let mut list = [Rectangle { width: 10, height: 1 }, Rectangle { width: 3, height: 5 }, Rectangle { width: 7, height: 12 }];
    list.sort_by_key(|r| r.width);
    for r in &list { println!("{}", r.width); }
    let mut total_width = 0;
    list.iter().for_each(|r| total_width += r.width);
    println!("total width = {total_width}");
    let missing: Option<Vec<i32>> = None;
    let fallback = missing.unwrap_or_else(Vec::new);
    println!("fallback len = {}", fallback.len());
}

Key points

  • The traits stack: every Fn closure is FnMut, and every FnMut closure is FnOnce
  • unwrap_or_else asks only for FnOnce() -> T, so it accepts every kind of closure — the loosest bound a caller can set
  • sort_by_key asks for FnMut.
  • It calls the closure many times while sorting.
  • A closure that moves a captured String out fails with E0507
  • The Fn traits describe how a closure may be called, from reading only to running once.

In this example, sort_by_key sorts three rectangles by width: the key closure |r| r.width captures nothing, so it easily meets the FnMut bound, and the widths print as 3, 7, 10. Next, for_each runs a closure that captures total_width by &mut borrow. The closure adds each width into it. Changing a captured value is exactly what FnMut allows. The total is 20. Below, unwrap_or_else needs only an FnOnce that returns a Vec. The plain function name Vec::new fits that bound. So the None value falls back to an empty Vec of length 0.

Note

Library authors choose the weakest bound that works.

Asking for FnOnce takes every closure.

Asking for Fn takes only the best-behaved ones.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why does sort_by_key reject a closure that moves a captured String out of its body?