Rust Closure Parameters
A closure parameter is a function parameter that accepts a closure. The function's signature names the closure kind it accepts, Fn, FnMut, or FnOnce.
The kind is written in the signature
A function that takes a closure must name the promise in its signature. F stands for the closure type. The where line states the promise. The promise is Fn, the strictest. The closure may only read what it captured. What it captured is held by &T:
fn apply_to_3<F>(f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(3)
}
println!("doubled = {}", apply_to_3(|x| 2 * x));impl Fn is the same promise, shorter
The impl spelling in the parameter list makes the same promise as the generic form. Here is the same function rewritten:
fn apply_to_3(f: impl Fn(i32) -> i32) -> i32 {
f(3)
}
println!("tripled = {}", apply_to_3(|x| 3 * x));FnMut: the closure may change its captures
The middle promise. The closure holds its captures by &mut T, so it can change them between calls:
fn run_twice<F>(mut f: F)
where
F: FnMut(),
{
f();
f();
}
let mut count = 0;
run_twice(|| count += 1);
println!("count = {count}");FnOnce: the loosest promise of all
FnOnce lets the closure take its captures by value, T. Every closure can make this promise. Even this one can. It moves its String out. So it can run only once:
fn run_once<F>(f: F) -> String
where
F: FnOnce() -> String,
{
f()
}
let name = String::from("Ferris");
let greet = move || name;
println!("hello {}", run_once(greet));Example
fn apply_to_3<F>(f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(3)
}
fn run_once<F>(f: F)
where
F: FnOnce(),
{
f();
}
fn main() {
let double = |x| 2 * x;
println!("3 doubled: {}", apply_to_3(double));
let name = String::from("Ferris");
let greet = move || println!("hello from {name}");
run_once(greet);
}Key points
- Choose the weakest promise your function needs.
- A function asking for FnOnce accepts closures that a function asking for Fn would refuse.
- The reverse is not true.
- Capture is decided one variable at a time.
- One closure may read one variable and drop another.
- The strongest need decides which traits the whole closure gets.
- unwrap_or_else asking for FnOnce is not decoration — it is the loosest promise, chosen so every closure is welcome.
- map and sort_by_key take closure parameters, and custom functions can take them too.
- A closure parameter lets a function accept a closure in its signature.
In this example, apply_to_3 is a generic function. F is any closure that promises Fn(i32) -> i32. The body calls f(3). The closure double doubles its input, so 3 doubled prints 6. Below, run_once asks only for FnOnce(). greet is a move closure that owns the String name and prints hello from Ferris. Because run_once promised so little, it accepts that move closure without complaint. An Fn bound would have refused it. An Fn bound refuses a closure that takes its capture by value.
Note
This is why library signatures look the way they do.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Which bound on a closure parameter accepts every possible closure?