Telex logo Telex

Rust Patterns That Matter #15: Fn, FnMut, FnOnce

Post 15 of 22 in Rust Patterns That Matter. Companion series: Building a Chat Server in Rust.

Previous: #14: Enum Dispatch | Next: #16: Storing Closures

You pass a closure to a function and get a trait mismatch. You didn't ask for any trait. You just wrote a || block. What's happening involves how closures capture their environment, and it's one of the most useful things to understand about Rust.

The motivation

fn call_twice(f: impl Fn()) {
    f();
    f();
}

fn main() {
    let mut count = 0;
    call_twice(|| {
        count += 1; // ERROR
    });
}
error[E0525]: expected a closure that implements the `Fn` trait,
              but this closure only implements `FnMut`

The closure mutates count. The function requires Fn, which doesn't allow mutation. The compiler knows what your closure does and what the function expects, and they don't match.

The three traits

Every closure in Rust implements at least one of three traits, determined by how it uses captured variables:

The hierarchy

 Fn       (most restrictive — only reads)
  ↑
 FnMut    (middle — reads and mutates)
  ↑
 FnOnce   (least restrictive — reads, mutates, consumes)

Every Fn closure is also FnMut (mutating nothing is a valid form of mutation). Every FnMut closure is also FnOnce (calling something once is a valid form of "at least once"). The hierarchy goes upward: if a function accepts FnOnce, you can pass any closure. If it requires Fn, only non-mutating closures qualify.

What determines the trait

The compiler picks the most permissive trait your closure qualifies for:

let name = String::from("Alice");

// Only reads `name` → implements Fn (and FnMut, and FnOnce)
let greet = || println!("Hello, {name}");

let mut count = 0;

// Mutates `count` → implements FnMut (and FnOnce), but NOT Fn
let mut increment = || count += 1;

let data = vec![1, 2, 3];

// Moves `data` out → implements FnOnce only
let consume = || drop(data);

The compiler examines every captured variable and how it's used. Reading: Fn. Mutating: FnMut. Moving out: FnOnce. The closure gets the least restrictive trait that covers all its captures.

The callee constrains it

The function you pass the closure to declares which trait it requires. That's the constraint:

// "I'll call this multiple times, read-only" → Fn
fn call_twice(f: impl Fn()) { f(); f(); }

// "I'll call this multiple times, it may mutate" → FnMut
fn call_twice_mut(mut f: impl FnMut()) { f(); f(); }

// "I'll call this exactly once" → FnOnce
fn call_once(f: impl FnOnce()) { f(); }

call_twice requires Fn because it calls the closure twice without exclusive access. If your closure mutates, it doesn't satisfy Fn, and the compiler rejects it. Change the requirement to FnMut and it works:

let mut count = 0;
call_twice_mut(|| count += 1); // OK — FnMut accepted
println!("{count}");              // 2

The move keyword

A common confusion: move changes ownership, not the trait. A move closure takes ownership of captured variables instead of borrowing them. But if it only reads those variables, it's still Fn:

let name = String::from("Alice");

let greet = move || println!("Hello, {name}");
// `name` is moved into the closure, but only read
// → still implements Fn

greet(); // fine
greet(); // fine — Fn, so can be called multiple times

move is about how data enters the closure (ownership vs borrowing). The trait is about how the closure uses that data (read, mutate, consume). These are independent.

When to use which

If you're writing a function that takes a closure:

Default to the least restrictive trait your function needs. If you only call the closure once, use FnOnce - it accepts all closures. Only require Fn if you genuinely need to call it multiple times without exclusive access.

See it in practice: Building a Chat Server #4: Commands and Plugins uses this pattern for the message filter plugin system.