When you need to hand a function a function
You've written a function that processes a list of numbers, and now somebody else wants to use it but with a different operation: square them, or filter out odd ones, or send each one to a logger. The polite thing is to let the caller pass in the operation. In JavaScript you'd take a callback. In Python you'd accept a function as an argument. In Rust the same idea exists, but it comes with a twist: the caller can hand you not just a function, but a snippet of code that captures variables from where it was written. That snippet is called a closure.
Callbacks built from closures are everywhere in idiomatic Rust. Iterator adapters use them constantly: map, filter, fold, for_each, find. Async runtimes spawn closures as tasks. Event loops register them as handlers. Once you're comfortable with the syntax and the trait bounds, callbacks stop feeling like a special technique and start feeling like a reflex.
The basic shape
// `apply` takes an i32 and a closure that turns one i32 into another.
// `F` is a generic type that's bounded to be callable in a specific way.
fn apply<F>(x: i32, f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
fn main() {
// `add_two` is a closure that captures nothing and returns its arg + 2.
let add_two = |x| x + 2;
// We pass the closure to `apply`. Rust figures out that add_two
// satisfies Fn(i32) -> i32 and lets it through.
let result = apply(5, add_two);
println!("{result}"); // prints 7
}
Three pieces are doing work here. The |x| x + 2 syntax is the closure literal: pipes around the parameter list, then an expression for the body. The <F> and where F: Fn(i32) -> i32 is how you say "this function accepts any closure as long as it has this calling shape." And the call f(x) invokes the closure like an ordinary function.
Notice that we never had to give the closure a type. Rust inferred everything: the parameter is an i32 (because of the bound), the return type is i32 (because the bound says so), and the closure can be called once or many times because it doesn't change any captured state.
What the trait bounds actually mean
That Fn(i32) -> i32 syntax is shorthand for a trait. There are three of them, and the difference matters when you start capturing variables.
Fn(...) -> R means "I can be called many times, and I only borrow my captures." Pure functions and read-only closures land here.
FnMut(...) -> R means "I can be called many times, and I might mutate my captures." Closures that update a counter or push to a vec live here.
FnOnce(...) -> R means "I can be called at most once, because calling me consumes my captures." Closures that move a String out and use it up land here.
Every closure implements FnOnce (you can always call it once). Many also implement FnMut. Closures that don't mutate or consume captures also implement Fn. So Fn is the strictest, FnOnce the loosest. When you write a function that takes a callback, pick the loosest bound that does what you need: this gives the caller the most freedom.
// Strict: caller's closure cannot mutate anything it captures.
fn each_strict<F: Fn(i32)>(items: &[i32], f: F) {
for x in items { f(*x); }
}
// Looser: caller's closure may mutate its captures.
// We need `mut f` because calling FnMut requires a mutable reference.
fn each_mut<F: FnMut(i32)>(items: &[i32], mut f: F) {
for x in items { f(*x); }
}
// Loosest, but only useful if you call it once.
fn run_once<F: FnOnce()>(f: F) {
f();
}
A closure that captures nothing at all, like |x| x + 2, satisfies all three because there's nothing to constrain.
The "ah-ha" moment with captures
Here's the surprising-but-true thing about closures. The capture mode (borrow, mutable borrow, or move) is determined by what your closure body does with the variable, not by what you write at the closure boundary. Rust looks at the body and picks the least invasive capture that makes the body work.
let s = String::from("hello");
// Body only reads `s`, so it captures by shared reference. `s` is
// still usable after the closure is created.
let printer = || println!("{s}");
printer();
printer();
println!("still have s: {s}");
If the body mutates a variable, Rust has to capture it mutably:
let mut count = 0;
// Body modifies `count`, so the closure captures it by mutable reference.
// We need `mut increment` so we can call it (FnMut requires &mut self).
let mut increment = || count += 1;
increment();
increment();
println!("{count}"); // prints 2
If the body consumes a value (sends it to another thread, returns it, drops it), Rust moves it in:
let s = String::from("hello");
// Body returns `s`, which means the closure has to own it.
// This closure is FnOnce because calling it gives s away.
let take = || s;
let owned = take();
// `take()` again wouldn't compile: s has been moved out.
println!("{owned}");
The move keyword forces a move regardless of what the body does. You'll see it most often when sending a closure to another thread:
use std::thread;
let data = vec![1, 2, 3];
// `move` says: capture `data` by value, even though the body just reads it.
// We need this because the spawned thread may outlive the current scope.
let handle = thread::spawn(move || {
println!("{data:?}");
});
handle.join().unwrap();
Without move, the spawned thread would borrow data from the parent stack frame, and the borrow checker would refuse because the parent might end first.
A more realistic callback: a tiny event bus
Let's build something a bit more substantial. An event bus that lets callers register a handler closure, then fires them all when an event happens.
// A bus that stores a list of boxed callbacks. Each callback takes
// an event by reference and returns nothing.
struct EventBus {
// Box<dyn ...> lets us store closures of different concrete types
// in the same Vec. Without Box, we'd need every closure to have
// the same type, which isn't possible.
handlers: Vec<Box<dyn Fn(&str)>>,
}
impl EventBus {
fn new() -> Self {
EventBus { handlers: Vec::new() }
}
// Register a handler. We accept anything implementing Fn(&str)
// and box it. `'static` means the closure can't borrow short-lived
// local data: it must own its captures or borrow only static stuff.
fn on<F: Fn(&str) + 'static>(&mut self, handler: F) {
self.handlers.push(Box::new(handler));
}
// Call every registered handler with the given event.
fn fire(&self, event: &str) {
for h in &self.handlers {
h(event);
}
}
}
fn main() {
let mut bus = EventBus::new();
let prefix = String::from("LOG:");
// `move` here so the closure owns `prefix`, satisfying 'static.
bus.on(move |e| println!("{prefix} {e}"));
bus.on(|e| println!("audit -> {e}"));
bus.fire("user_signed_up");
bus.fire("user_logged_out");
}
Two things to call out. The Box<dyn Fn(&str)> is a trait object: it's how you store many different closures in one collection. And the + 'static on the trait bound keeps things safe: the bus could outlive whatever scope registered the callback, so the callback can't keep a borrow into that scope.
Common pitfalls
You wrote Fn(...) when you needed FnMut. The compiler error reads like:
error[E0596]: cannot borrow `f` as mutable, as it is not declared as mutable
or
error[E0594]: cannot assign to `count`, as it is a captured variable in a `Fn` closure
Fix: change the bound to FnMut and the parameter to mut f.
You captured a borrow that didn't live long enough. Often shows up when you try to return a closure from a function:
fn make() -> impl Fn() {
let s = String::from("oops");
|| println!("{s}") // closure borrows s, but s dies here
}
The error mentions a lifetime that's "more general than" or that "does not live long enough." Fix: use move so the closure owns s:
fn make() -> impl Fn() {
let s = String::from("ok");
move || println!("{s}")
}
You stored closures of different shapes in a Vec. The compiler refuses because each closure has its own anonymous type. Fix: box them as trait objects, Vec<Box<dyn Fn(...)>>.
You hit "the trait FnOnce is not implemented." Usually means you tried to pass a closure that consumes a capture to something expecting Fn. Reach for an owned clone, or restructure so the closure doesn't consume.
When to use what
If your callback never mutates anything and you're calling it many times, take Fn. Iterator combinators like map and filter are the canonical example.
If your callback updates a counter, accumulator, or any external state, take FnMut. Iterator::for_each actually takes FnMut internally for this reason.
If your callback is going to be called exactly once (especially common in async one-shot tasks), FnOnce is the cleanest signature. Don't tighten it just because you can.
If you need to store callbacks in a struct or send them across threads, you almost always want Box<dyn Fn...> + 'static plus Send for the threaded case.
Where to go next
Closures are the entry point to a much bigger story about iterators, async, and traits.
What Are Closures in Rust and How Do They Work?