Closures

The Fn Trait

The Fn trait in std::ops is the most capability-rich of the three closure traits, representing closures and functions that can be called repeatedly through a shared immutable reference (&self) without mutating or consuming their captured environment.

Conceptual Definition

pub trait Fn<Args>: FnMut<Args> {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

Because Fn takes &self:

  • An Fn closure can be called any number of times, including concurrently across multiple threads (when Sync) or recursively via shared references.
  • Fn is a subtrait of FnMut (which in turn is a subtrait of FnOnce), meaning any Fn closure can also be passed wherever FnMut or FnOnce is required.

Which Callables Implement Fn

Callable Kind Implements Fn? Reason
Plain functions (fn) and non-capturing closures Yes No captured state to mutate or consume
Closure capturing variables only by shared reference (&T) Yes Reading &T through &self is always valid
move closure that owns T and only reads T in its body Yes Owning T inside the closure struct still allows reading &self.t via &self
Closure that mutates or moves out of a captured variable No Requires &mut self (FnMut) or self (FnOnce)

Calling an Fn Closure Repeatedly and Through Shared References

fn run_twice<F>(f: &F, input: i32) -> (i32, i32)
where
    F: Fn(i32) -> i32,
{
    (f(input), f(input + 1))
}

fn main() {
    let offset = 100;

    // Captures `offset` by shared reference -> implements `Fn(i32) -> i32`
    let add_offset = |x: i32| x + offset;

    let (first, second) = run_twice(&add_offset, 5);
    println!("first = {first}, second = {second}");

    // `move` closure that owns a `String` but only reads it ALSO implements `Fn`:
    let prefix = String::from("ID");
    let format_id = move |id: u32| format!("{prefix}-{id:04}");
    println!("{}, {}", format_id(1), format_id(2));
}
first = 105, second = 106
ID-0001, ID-0002

Notice the distinction between how a closure captures (move vs. borrow) and which Fn* traits it implements: format_id above is a move closure that owns prefix, yet it still implements Fn because its body only reads prefix (&self.prefix).