Q: Fn, FnMut, FnOnce — what's the difference?

Answer:

Closures in Rust implement one of three traits depending on how they capture their environment. The compiler picks the most permissive trait automatically. Understanding the hierarchy explains "cannot move out of captured variable" errors.

The Hierarchy

        FnOnce        (called at least once, may consume captures)
          ▲
          │ super-trait
          │
         FnMut         (called many times, may mutate captures)
          ▲
          │ super-trait
          │
          Fn           (called many times, only borrows immutably)

Every Fn is also FnMut and FnOnce. Every FnMut is also FnOnce. Not vice versa.

The Three Capture Modes

#![allow(unused)]
fn main() {
let s = String::from("hi");

// Fn — captures by &
let print = || println!("{}", s);
print(); print();                 // OK, called many times

// FnMut — captures by &mut
let mut v = vec![1, 2, 3];
let mut push = |x| v.push(x);
push(4); push(5);                 // OK, mutates

// FnOnce — captures by value (move)
let owner = move || drop(s);
owner();                          // OK
// owner();                       // ERROR: value moved
}

Compiler infers the minimum required. move keyword forces by-value capture (turns a Fn into a Fn-that-owns).

When Each Is Required

Function signatures pick the looseness:

#![allow(unused)]
fn main() {
fn run_once<F: FnOnce()>(f: F)   { f(); }
fn run_many<F: FnMut()>(mut f: F) { f(); f(); }
fn run_shared<F: Fn()>(f: F)     { f(); f(); }
}
  • FnOnce is the most permissive bound — accepts any closure, but you can only call it once.
  • Fn is the most restrictive bound — but supports &F (shared) and calling from multiple places.

Rule of thumb: take the least restrictive bound your code needs.

Function Pointers fn

fn(T) -> U is a separate type — a pointer to a free function, no environment. Coerces to all three traits.

#![allow(unused)]
fn main() {
fn double(x: i32) -> i32 { x * 2 }

let f: fn(i32) -> i32 = double;          // function pointer
let c: Box<dyn Fn(i32) -> i32> = Box::new(double);   // also fine
}

A closure with no captures also coerces to fn. Useful for callbacks across FFI:

#![allow(unused)]
fn main() {
extern "C" fn callback(x: i32) { ... }
unsafe { c_register(callback as *const u8); }
}

Moving Captures: move Closures

#![allow(unused)]
fn main() {
let s = String::from("hi");
let closure = move || println!("{}", s);
// s no longer usable — moved into closure
}

Used heavily with thread::spawn and async tasks, where the closure outlives the caller's scope.

Returning Closures

You cannot return a bare closure — its size is unknown.

#![allow(unused)]
fn main() {
fn make() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

fn make_boxed() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}
}
  • impl Fn — static dispatch, type known at compile time.
  • Box<dyn Fn> — dynamic dispatch, can return different concrete closures from branches.

Returning Different Closures From Branches

#![allow(unused)]
fn main() {
// ❌ ERROR — two different closure types
fn pick(neg: bool) -> impl Fn(i32) -> i32 {
    if neg { |x| -x } else { |x| x }
}

// ✅ works with dyn
fn pick(neg: bool) -> Box<dyn Fn(i32) -> i32> {
    if neg { Box::new(|x| -x) } else { Box::new(|x| x) }
}
}

Two |...| literals are two distinct types even if signatures match. impl Trait requires one concrete type.

Capturing &self and Method Closures

#![allow(unused)]
fn main() {
struct Counter(u32);
impl Counter {
    fn incrementer(&mut self) -> impl FnMut() + '_ {
        move || self.0 += 1
    }
}
}

'_ lifetime ties the closure to &mut self. The closure can't outlive the borrow.

Common Errors

#![allow(unused)]
fn main() {
let mut v = vec![1];
let f = || v.push(2);
f();                                 // ERROR: closure was inferred Fn but needs FnMut
}

Fix: let mut f = || v.push(2);FnMut closures need mut.

#![allow(unused)]
fn main() {
let s = String::from("hi");
let f = || drop(s);
f();
f();                                 // ERROR: FnOnce can only be called once
}

Fix: don't drop captured value, or rebuild it inside.

Async Closures

Returning async {} from a closure gives FnOnce -> impl Future. Stable async closures (Rust 1.85+) help:

#![allow(unused)]
fn main() {
let process = async |x: u32| { fetch(x).await };
}

Before stable async closures, the common workaround: a closure returning async move { ... } block.

Cheat Sheet

Use caseTraitCapture
Pure transform, reusedFn&T
Stateful iteratorFnMut&mut T
Spawned task / one-shotFnOnceT (by value)
Callback listBox<dyn Fn> or Box<dyn FnMut>varies
Cross-threadFnOnce + Send + 'staticmove

Common Mistakes

MistakeFix
F: Fn when you need state mutationF: FnMut and accept mut f: F
Returning closure without Box/implUse impl Fn for single concrete; Box<dyn> for runtime
Captures last too long, causing borrow errorsAdd move, or restructure data ownership
Using FnOnce in a loopCompile error — call it again? Wrap with Option::take if you must

[!NOTE] If a function takes a closure, prefer FnOnce first, relax to FnMut, then Fn only if you call repeatedly with shared access. Lower friction for callers.

Interview Follow-ups

  • "What's the size of a closure?" — Sum of its captured environment, padded. A no-capture closure is zero-sized.
  • "How is a closure compiled?" — Anonymous struct holding captures + impl Fn* method body.
  • "fn vs Fn — what's the distinction?" — Lowercase fn is a function pointer type. Uppercase Fn is a trait. Function pointers implement Fn.