19.2Closures and the Fn traits
In the previous lesson (19.1) every closure stayed put: we made it and called it in the same function. The reason closures are everywhere in real Rust, though, is that you pass them to other functions. You tell sort_by how to compare, you tell filter what to keep, you tell a button what to do when clicked. To write a function that accepts a closure, you need a way to name a closure's type in a signature. That's what the Fn traits are for.
Each closure has its own anonymous type
Here's the wrinkle. Two closures that look identical do not share a type:
let a = |x: i32| x + 1;
let b = |x: i32| x + 1;
a and b have different types, each one a unique, compiler-generated type with no name you could write. That makes sense once you remember a closure carries its captures: a closure is really a little anonymous struct holding the captured variables, with a call method attached. Two such structs are different types even when their code matches, the same way two different structs are different types.
So you can't write fn apply(f: ???) by naming the closure type, because it has no name. What closures do share is a trait: the trait that says "this can be called like a function." Three of them, actually, and the difference between them is exactly the chapter-8-and-9 distinction you already know.
Fn, FnMut, FnOnce
A closure captures its environment by reference, by mutable reference, or by value (lesson 19.1). Calling it does the corresponding thing to those captures, and that determines which traits it implements:
FnOnce is the most permissive: it can be called, but possibly only once, because calling it might consume (move out) a captured value. Every closure implements FnOnce, since every closure can be called at least once.
FnMut can be called repeatedly and might mutate its captured values when called. It needs &mut access to the closure to do so.
Fn can be called repeatedly and only reads its captures (or captures nothing). It needs only & access, so it can even be called from several places at once.
These nest: every Fn is also an FnMut (reading repeatedly is a special case of mutating repeatedly), and every FnMut is also an FnOnce (anything callable many times is callable once). So when you write a function that takes a closure, you want the most permissive bound that your code actually needs, because that accepts the most closures. If you only call the closure once, ask for FnOnce. If you call it many times but don't need it to mutate, ask for Fn.
Taking a closure as a parameter
The cleanest way to accept a closure is impl Trait in argument position, the syntax from lesson 16.4. Here's a function that calls the closure it's given twice, so it needs Fn:
fn do_twice(f: impl Fn()) {
f();
f();
}
fn main() {
let name = String::from("world");
do_twice(|| println!("hello, {name}"));
}hello, world
hello, world
impl Fn() reads "some type that implements Fn and takes no arguments and returns nothing." The closure || println!("hello, {name}") only reads name, so it implements Fn, and it's accepted. Because the bound is Fn, do_twice may call f as many times as it likes.
To accept a closure that returns a value, name the signature inside the trait. Fn(i32) -> i32 means "callable with one i32, returning an i32":
fn apply_to_5(f: impl Fn(i32) -> i32) -> i32 {
f(5)
}
fn main() {
let factor = 3;
println!("{}", apply_to_5(|x| x * factor)); // captures factor
println!("{}", apply_to_5(|x| x + 1));
}15
6
Both closures match Fn(i32) -> i32, and the first one quietly carries factor along. The caller of apply_to_5 thinks only about the signature; the captures are the closure's private business.
If your function needs to mutate through the closure, ask for FnMut, and the parameter must be mut:
fn call_n_times(mut f: impl FnMut(), n: usize) {
for _ in 0..n {
f();
}
}
fn main() {
let mut count = 0;
call_n_times(|| count += 1, 3);
println!("{count}");
}3Best practice
When writing a function that takes a closure, ask for the most permissive trait your body actually requires: FnOnce if you call it at most once, FnMut if you call it repeatedly and need mutation, Fn if you only read. A looser bound accepts more closures. Reach for a tighter one only when your code genuinely needs that capability.
Returning a closure
A closure has no nameable type, so to return one you again use impl Trait, this time in return position. There's one catch: a returned closure usually captures locals from the function, and those locals are about to disappear when the function ends, so the closure must own its captures. That's the move from lesson 19.1, now load-bearing:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
fn main() {
let add_ten = make_adder(10);
println!("{}", add_ten(5));
println!("{}", add_ten(100));
}15
110
make_adder returns "some Fn(i32) -> i32." The move is mandatory: without it the closure would borrow n, but n is a local that dies when make_adder returns, so the borrow would dangle, exactly the dangling-reference error chapter 9 trained you to expect. With move, the closure owns its own copy of n and is free to outlive the function that built it. This is the standard recipe for "a function that builds and hands back behavior."
Function pointers, briefly
Closures that capture nothing are interchangeable with plain functions, and both can be passed where an Fn is expected. There's also a concrete type for "a plain function, no captures": the function pointer fn (lowercase, a type, distinct from the fn keyword that defines functions):
fn double(x: i32) -> i32 { x * 2 }
fn apply(f: fn(i32) -> i32, x: i32) -> i32 {
f(x)
}
fn main() {
println!("{}", apply(double, 5)); // a named function
println!("{}", apply(|x| x * 2, 5)); // a non-capturing closure
}10
10
A fn pointer accepts both named functions and non-capturing closures, but never a capturing one, since there's nowhere to store the captures. In practice you'll reach for the Fn traits the vast majority of the time, because they accept everything fn does plus capturing closures. The fn pointer type is worth recognizing when you meet it (it shows up in C interop, chapter 25, where captures aren't an option), but impl Fn is the default tool.
For advanced readers
impl Fn in return position works for a single closure type. If a function has two branches that each return a different closure, they have different anonymous types and impl Fn won't unify them, you'll get a type-mismatch error. The fix is a trait object on the heap, Box<dyn Fn(i32) -> i32> (trait objects from lesson 16.9, Box from chapter 21), which erases the concrete types behind one pointer. Reach for it only when you actually need to return different closures from different paths.
Quiz time
Question #1
Why can't you write a function parameter type as the literal type of a specific closure?
Show solution
Because each closure has its own unique, compiler-generated anonymous type with no name you can write in source. Even two textually identical closures have different types. You name closures in signatures through the traits they share instead, Fn, FnMut, or FnOnce (via impl Fn(...) -> ... or a generic bound), not through a concrete type name.
Question #2
What's the difference between Fn, FnMut, and FnOnce, and which should a function parameter ask for?
Show solution
Fn only reads its captures and can be called repeatedly; FnMut may mutate its captures and can be called repeatedly (needs &mut to the closure); FnOnce may consume its captures and so might be callable only once. They nest: Fn ⊂ FnMut ⊂ FnOnce. A parameter should request the most permissive trait its body needs (FnOnce if called at most once, then FnMut, then Fn), because looser bounds accept more closures.
Question #3
Why does this fail to compile, and what one keyword fixes it?
fn make_greeter(name: String) -> impl Fn() {
|| println!("Hi, {name}")
}Show solution
The closure borrows name, but name is a local that's dropped when make_greeter returns, so the returned closure would hold a dangling reference (a chapter-9 error). Add move before the closure (move || println!("Hi, {name}")) so it takes ownership of name and can outlive the function.
You can now pass behavior around as a value. The next lesson (19.3) introduces the other half of the chapter: the Iterator trait, the abstraction that closures were really invented to feed.