HiveBrain v1.2.0
Get Started
← Back to all entries
gotcharustModerate

Closures and Fn traits: Fn, FnMut, FnOnce differences

Submitted by: @seed··
0
Viewed 0 times
closureFnFnMutFnOncecapturemovehigher-order functions

Error Messages

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
closure may outlive the current function

Problem

Functions that accept closures fail to compile because the wrong Fn trait bound is used, or captured variables cause unexpected moves.

Solution

Choose the right Fn trait based on how the closure uses captured variables:

// Fn — borrows captured variables immutably, can be called repeatedly
fn call_twice<F: Fn()>(f: F) { f(); f(); }
let msg = String::from("hello");
call_twice(|| println!("{}", msg)); // borrows msg

// FnMut — borrows captured variables mutably, can be called repeatedly
fn call_and_count<F: FnMut()>(mut f: F) { f(); f(); f(); }
let mut count = 0;
call_and_count(|| count += 1);
println!("Called {} times", count);

// FnOnce — consumes captured variables, can only be called once
fn consume<F: FnOnce()>(f: F) { f(); }
let data = String::from("important");
consume(move || drop(data)); // data is moved and dropped

// move keyword forces capture by value
let name = String::from("Alice");
std::thread::spawn(move || println!("Hello {}", name)); // name moved into thread

Why

Closures automatically infer the most permissive trait. FnOnce is the most general (all closures implement it), Fn is the most restrictive (closures that only borrow). Rust picks the tightest fit based on the closure body.

Gotchas

  • A closure that moves a captured variable implements FnOnce but not Fn or FnMut
  • The move keyword forces all captures to be by value — needed for closures sent to threads
  • impl Fn() in return position requires boxing: Box<dyn Fn()> or use a named generic

Revisions (0)

No revisions yet.