Lesson 37 of 94
Fn, FnMut & FnOnce
Every closure implements one or more of three traits, depending on what it does with the variables it captures:
| Trait | The closure... | Can be called |
|---|---|---|
FnOnce |
may consume captured values | once |
FnMut |
may change captured values | many times, needs &mut access |
Fn |
only reads captured values | many times, even concurrently |
They form a hierarchy: every Fn is also an FnMut, and every FnMut is also an FnOnce. A function that accepts a closure picks the least demanding bound it can:
- Accept
FnOnceif you call it at most once. This accepts every closure. - Accept
FnMutif you call it several times, one call at a time. - Accept
Fnif you need to call it through a shared reference, or from several threads.
1. A closure that only reads is Fn
fn call_twice(f: impl Fn() -> String) -> String {
format!("{} / {}", f(), f())
}
fn main() {
let name = String::from("Ferris");
let greet = || format!("hi {name}");
println!("{}", call_twice(greet));
println!("{name} is still ours");
}hi Ferris / hi Ferris
Ferris is still ours2. A closure that changes state is FnMut
fn call_three_times(mut f: impl FnMut() -> u32) -> Vec<u32> {
vec![f(), f(), f()]
}
fn main() {
let mut next_id = 100;
let ids = call_three_times(|| {
next_id += 1;
next_id
});
println!("{:?}, next_id is now {next_id}", ids);
}[101, 102, 103], next_id is now 103The parameter is declared mut f, because calling an FnMut changes it.
3. An FnMut closure where Fn is required
A function that asks for Fn promises callers it won't cause side effects through the closure, so a mutating closure is rejected.
fn call_twice(f: impl Fn()) {
f();
f();
}
fn main() {
let mut count = 0;
call_twice(|| count += 1);
println!("{count}");
}error[E0594]: cannot assign to `count`, as it is a captured variable in a `Fn` closure
--> src/main.rs:8:19
|
1 | fn call_twice(f: impl Fn()) {
| --------- change this to accept `FnMut` instead of `Fn`
...
7 | let mut count = 0;
| --------- `count` declared here, outside the closure
8 | call_twice(|| count += 1);
| ---------- -- ^^^^^^^^^^ cannot assign
| | |
| | in this closure
| expects `Fn` instead of `FnMut`4. A closure that consumes a capture is only FnOnce
If the closure moves a captured value out (by returning it, or passing it to a function that takes ownership), it can only run once: after that, the value is gone.
fn run_once(f: impl FnOnce() -> Vec<String>) -> usize {
let items = f();
items.len()
}
fn main() {
let batch = vec![String::from("a"), String::from("b")];
let take_batch = move || batch; // returns the captured Vec: consumes it
println!("{} items", run_once(take_batch));
}2 items5. Calling an FnOnce closure twice
fn main() {
let report = String::from("quarterly report");
let send = move || {
let sent = report; // moves the String out of the closure
println!("sent {sent}");
};
send();
send();
}error[E0382]: use of moved value: `send`
--> src/main.rs:8:5
|
7 | send();
| ------ `send` moved due to this call
8 | send();
| ^^^^ value used here after move
|
note: closure cannot be invoked more than once because it moves the variable `report` out of its environment
--> src/main.rs:4:20
|
4 | let sent = report; // moves the String out of the closure
| ^^^^^^
note: this value implements `FnOnce`, which causes it to be moved when called
--> src/main.rs:7:5
|
7 | send();
| ^^^^6. Passing an FnOnce closure where FnMut is required
fn call_twice(mut f: impl FnMut()) {
f();
f();
}
fn main() {
let token = String::from("one-time-token");
let revoke = move || drop(token);
call_twice(revoke);
}error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
--> src/main.rs:8:18
|
8 | let revoke = move || drop(token);
| ^^^^^^^ ----- closure is `FnOnce` because it moves the variable `token` out of its environment
| |
| this closure implements `FnOnce`, not `FnMut`
9 | call_twice(revoke);
| ---------- ------ the requirement to implement `FnMut` derives from here
| |
| required by a bound introduced by this call
|
note: required by a bound in `call_twice`
--> src/main.rs:1:27
|
1 | fn call_twice(mut f: impl FnMut()) {
| ^^^^^^^ required by this bound in `call_twice`7. move doesn't decide the trait: the body does
move controls how values are captured (by value). What the body does with them decides the trait. A move closure that only reads is still Fn.
fn call_twice(f: impl Fn() -> usize) -> usize {
f() + f()
}
fn main() {
let words = vec!["alpha", "beta"];
let count = move || words.len(); // owns `words`, but only reads it: Fn
println!("{}", call_twice(count));
}48. Why FnOnce is the most flexible bound
Because every closure is at least FnOnce, a function that takes FnOnce accepts anything. Option::unwrap_or_else and thread::spawn use FnOnce for exactly this reason: they call the closure at most once.
fn with_default<T>(value: Option<T>, fallback: impl FnOnce() -> T) -> T {
match value {
Some(v) => v,
None => fallback(),
}
}
fn main() {
let owned_default = String::from("guest");
let reads = |x: Option<u8>| with_default(x, || 7); // Fn closure
let mut calls = 0;
let mutates = with_default(None, || { calls += 1; calls }); // FnMut closure
let consumes = with_default(None, move || owned_default); // FnOnce closure
println!("{} {} {} {mutates} {consumes}", reads(Some(1)), reads(None), calls);
}1 7 1 1 guest9. Generic bounds: F: Fn(T) -> U
impl Fn... in parameter position is shorthand for a generic parameter. The long form is needed when you want to name the type, for example to store it in a struct or use it twice.
fn map_all<T, U, F>(items: &[T], f: F) -> Vec<U>
where
F: Fn(&T) -> U,
{
let mut out = Vec::with_capacity(items.len());
for item in items {
out.push(f(item));
}
out
}
fn main() {
let prices_cents = [1_999, 450, 12_000];
let formatted = map_all(&prices_cents, |c| format!("${}.{:02}", c / 100, c % 100));
let doubled = map_all(&prices_cents, |c| c * 2);
println!("{:?} {:?}", formatted, doubled);
}["$19.99", "$4.50", "$120.00"] [3998, 900, 24000]10. Taking a closure by reference: &dyn Fn and &mut dyn FnMut
When a function only needs to borrow a closure, taking &dyn Fn avoids generics and compiles once for every closure type.
fn apply_n(f: &dyn Fn(i32) -> i32, n: usize, start: i32) -> i32 {
let mut x = start;
for _ in 0..n {
x = f(x);
}
x
}
fn visit_all(items: &[&str], visitor: &mut dyn FnMut(&str)) {
for item in items {
visitor(item);
}
}
fn main() {
println!("{}", apply_n(&|x| x * 2, 10, 1));
let mut longest = String::new();
visit_all(&["kiwi", "banana", "fig"], &mut |s| {
if s.len() > longest.len() {
longest = s.to_string();
}
});
println!("longest: {longest}");
}1024
longest: banana11. Storing an FnMut in a struct
A struct holding a stateful closure must call it through &mut self.
struct IdGenerator<F: FnMut() -> u64> {
next: F,
issued: Vec<u64>,
}
impl<F: FnMut() -> u64> IdGenerator<F> {
fn issue(&mut self) -> u64 {
let id = (self.next)();
self.issued.push(id);
id
}
}
fn main() {
let mut counter = 1000;
let mut ids = IdGenerator {
next: move || {
counter += 7;
counter
},
issued: Vec::new(),
};
ids.issue();
ids.issue();
println!("{} -> issued {:?}", ids.issue(), ids.issued);
}1021 -> issued [1007, 1014, 1021]12. Box<dyn FnOnce>: a task queue
Boxed FnOnce closures are how you store "jobs to run later, once each". Calling a Box<dyn FnOnce()> consumes the box.
fn main() {
let mut jobs: Vec<Box<dyn FnOnce() -> String>> = Vec::new();
let report = String::from("q3-report.pdf");
let user = String::from("ana@example.com");
jobs.push(Box::new(move || format!("emailed {report}")));
jobs.push(Box::new(move || format!("deleted account {user}")));
jobs.push(Box::new(|| String::from("cleared cache")));
for job in jobs {
println!("{}", job());
}
}emailed q3-report.pdf
deleted account ana@example.com
cleared cache13. Returning different closures with Box<dyn Fn>
-> impl Fn must return one concrete closure. To choose between closures at runtime, box them.
fn pricing_rule(kind: &str) -> Box<dyn Fn(u64) -> u64> {
match kind {
"student" => Box::new(|cents| cents / 2),
"bulk" => Box::new(|cents| cents - cents / 10),
"fixed" => {
let fee = 500;
Box::new(move |cents| cents + fee)
}
_ => Box::new(|cents| cents),
}
}
fn main() {
for kind in ["student", "bulk", "fixed", "regular"] {
println!("{kind:<8} 10000 -> {}", pricing_rule(kind)(10_000));
}
}student 10000 -> 5000
bulk 10000 -> 9000
fixed 10000 -> 10500
regular 10000 -> 1000014. Closures are often zero-sized
A closure is a struct generated by the compiler, holding its captured values. A closure that captures nothing takes no memory at all, and calling it through a generic parameter is as fast as calling a normal function.
fn size_of_val<T>(_: &T) -> usize {
std::mem::size_of::<T>()
}
fn main() {
let a = 1_u64;
let b = String::from("text");
let nothing = || 42;
let one_u64 = move || a + 1;
let one_ref = || b.len();
println!("captures nothing: {} bytes", size_of_val(¬hing));
println!("moves a u64: {} bytes", size_of_val(&one_u64));
println!("borrows a String: {} bytes", size_of_val(&one_ref));
let owned_string = move || b.len();
println!("moves a String: {} bytes", size_of_val(&owned_string));
println!("{} {} {}", nothing(), one_u64(), owned_string());
}captures nothing: 0 bytes
moves a u64: 8 bytes
borrows a String: 8 bytes
moves a String: 24 bytes
42 2 4A Rust closure is a plain value whose size is exactly what it captures.
15. Composing functions
A generic compose returns a new closure that runs one function and then another.
fn compose<A, B, C>(f: impl Fn(A) -> B, g: impl Fn(B) -> C) -> impl Fn(A) -> C {
move |x| g(f(x))
}
fn main() {
let trim_then_upper = compose(|s: &str| s.trim().to_string(), |s: String| s.to_uppercase());
let parse_then_double = compose(|s: &str| s.parse::<i32>().unwrap_or(0), |n| n * 2);
let slugify = compose(trim_then_upper, |s: String| s.replace(' ', "-"));
println!("{}", slugify(" hello rust world "));
println!("{}", parse_then_double("21"));
}HELLO-RUST-WORLD
4216. Partial application and currying
A function can fix some arguments and return a closure taking the rest.
fn tax(rate_percent: u64) -> impl Fn(u64) -> u64 {
move |cents| cents * rate_percent / 100
}
fn between(min: i32, max: i32) -> impl Fn(i32) -> bool {
move |x| x >= min && x <= max
}
fn main() {
let uk_vat = tax(20);
let de_vat = tax(19);
println!("{} {}", uk_vat(10_000), de_vat(10_000));
let is_teen = between(13, 19);
let ages = [8, 13, 17, 21];
let teens: Vec<i32> = ages.iter().copied().filter(|&a| is_teen(a)).collect();
println!("{:?}", teens);
}2000 1900
[13, 17]17. Memoization with an FnMut closure
A closure can own a cache and fill it as it runs.
use std::collections::HashMap;
fn main() {
let mut cache: HashMap<u64, u64> = HashMap::new();
let mut computations = 0;
let mut slow_square = |n: u64| -> u64 {
*cache.entry(n).or_insert_with(|| {
computations += 1;
n * n // pretend this is expensive
})
};
let results: Vec<u64> = [3, 4, 3, 3, 4, 5].iter().map(|&n| slow_square(n)).collect();
println!("{:?}", results);
println!("computed {computations} times for 6 calls");
}[9, 16, 9, 9, 16, 25]
computed 3 times for 6 calls18. A closure that captures a closure
Closures are values, so they can capture other closures, which gives you higher-order building blocks.
fn main() {
let is_even = |n: &i32| n % 2 == 0;
let is_positive = |n: &i32| *n > 0;
let both = |n: &i32| is_even(n) && is_positive(n);
let either = move |n: &i32| is_even(n) || is_positive(n);
let values = [-4, -3, 0, 5, 8];
println!("{:?}", values.iter().filter(|n| both(n)).collect::<Vec<_>>());
println!("{:?}", values.iter().filter(|n| either(n)).collect::<Vec<_>>());
}[8]
[-4, 0, 5, 8]19. Configuration closures: the |cfg| { ... } pattern
Libraries often let you configure an object by passing a closure that receives a &mut to it. It keeps construction in the library's hands while giving you full control.
#[derive(Debug, Default)]
struct ServerConfig {
port: u16,
workers: usize,
tls: bool,
}
fn start_server(configure: impl FnOnce(&mut ServerConfig)) -> ServerConfig {
let mut cfg = ServerConfig { port: 8080, workers: 4, tls: false };
configure(&mut cfg);
cfg
}
fn main() {
let dev = start_server(|_| {});
let prod = start_server(|cfg| {
cfg.port = 443;
cfg.workers = 32;
cfg.tls = true;
});
println!("{:?}\n{:?}", dev, prod);
}ServerConfig { port: 8080, workers: 4, tls: false }
ServerConfig { port: 443, workers: 32, tls: true }20. Closures versus traits for strategies
A single-method trait and a closure bound express the same idea. Closures are lighter for one-off behavior; traits are better when the behavior has a name, several methods, or its own state and tests.
trait Scorer {
fn score(&self, text: &str) -> u32;
}
struct LengthScorer;
impl Scorer for LengthScorer {
fn score(&self, text: &str) -> u32 {
text.len() as u32
}
}
fn best_by_trait<'a>(items: &[&'a str], s: &impl Scorer) -> &'a str {
items.iter().copied().max_by_key(|t| s.score(t)).unwrap_or("")
}
fn best_by_closure<'a>(items: &[&'a str], score: impl Fn(&str) -> u32) -> &'a str {
items.iter().copied().max_by_key(|t| score(t)).unwrap_or("")
}
fn main() {
let titles = ["Rust", "Ownership in depth", "Why Rust?"];
println!("{}", best_by_trait(&titles, &LengthScorer));
println!("{}", best_by_closure(&titles, |t| t.matches(' ').count() as u32));
println!("{}", best_by_closure(&titles, |t| t.contains('?') as u32));
}Ownership in depth
Ownership in depth
Why Rust?21. Realistic example: a middleware chain built from closures
Each middleware takes the request and a next function. Composing them produces the final handler.
type Handler = Box<dyn Fn(&str) -> String>;
fn with_logging(next: Handler) -> Handler {
Box::new(move |req| {
let resp = next(req);
format!("[log] {req} -> {resp}")
})
}
fn with_auth(token: &'static str, next: Handler) -> Handler {
Box::new(move |req| {
if req.contains(token) { next(req) } else { String::from("401") }
})
}
fn main() {
let app: Handler = Box::new(|req| if req.starts_with("GET /health") { String::from("200 ok") } else { String::from("404") });
let app = with_auth("key=secret", app);
let app = with_logging(app);
println!("{}", app("GET /health?key=secret"));
println!("{}", app("GET /health"));
println!("{}", app("GET /nope?key=secret"));
}[log] GET /health?key=secret -> 200 ok
[log] GET /health -> 401
[log] GET /nope?key=secret -> 404type Handler = ... gives a long type a short name, a type alias.
22. Realistic example: lazy validation with short-circuiting
Checks are closures run in order, so an expensive check (here, a pretend remote lookup) only runs when the cheap ones pass. The checks are &mut dyn FnMut, so they can keep state such as a call counter.
fn validate(input: &str, checks: &mut [(&str, &mut dyn FnMut(&str) -> bool)]) -> Result<(), String> {
for (name, check) in checks.iter_mut() {
if !check(input) {
return Err(format!("failed: {name}"));
}
}
Ok(())
}
fn main() {
let reserved = ["admin", "root"];
let mut remote_calls = 0;
let mut well_formed = |s: &str| s.len() >= 3 && s.chars().all(|c| c.is_ascii_alphanumeric());
let mut not_reserved = |s: &str| !reserved.contains(&s);
let mut available = |s: &str| {
remote_calls += 1; // pretend this is a slow network call
s != "taken"
};
for name in ["ab", "admin", "taken", "ferris"] {
let result = validate(
name,
&mut [("format", &mut well_formed), ("reserved", &mut not_reserved), ("available", &mut available)],
);
println!("{name:>7}: {:?}", result);
}
println!("remote lookups: {remote_calls}");
} ab: Err("failed: format")
admin: Err("failed: reserved")
taken: Err("failed: available")
ferris: Ok(())
remote lookups: 223. Realistic example: an undo stack of closures
Each action pushes a closure that knows how to undo it. Undo runs the most recent one, once.
fn main() {
let mut document = vec![String::from("Title")];
let mut undo: Vec<Box<dyn FnOnce(&mut Vec<String>)>> = Vec::new();
document.push(String::from("First paragraph"));
undo.push(Box::new(|doc| {
doc.pop();
}));
let old_title = std::mem::replace(&mut document[0], String::from("Better Title"));
undo.push(Box::new(move |doc| doc[0] = old_title));
println!("{:?}", document);
while let Some(action) = undo.pop() {
action(&mut document);
println!("undo -> {:?}", document);
}
}["Better Title", "First paragraph"]
undo -> ["Title", "First paragraph"]
undo -> ["Title"]24. Realistic example: a rate-limited callback
A wrapper returns a new FnMut that forwards to the original only when enough time has passed. The clock is passed in, which keeps the example deterministic.
fn throttle<F: FnMut(&str)>(interval_ms: u64, mut f: F) -> impl FnMut(u64, &str) {
let mut last_call: Option<u64> = None;
move |now_ms, msg| {
let ready = last_call.is_none_or(|t| now_ms - t >= interval_ms);
if ready {
last_call = Some(now_ms);
f(msg);
}
}
}
fn main() {
let mut delivered = Vec::new();
{
let mut alert = throttle(1_000, |msg: &str| delivered.push(msg.to_string()));
for (t, msg) in [(0, "cpu 91%"), (200, "cpu 93%"), (900, "cpu 95%"), (1_100, "cpu 97%"), (2_500, "cpu 90%")] {
alert(t, msg);
}
}
println!("{:?}", delivered);
}["cpu 91%", "cpu 97%", "cpu 90%"]The inner block ends the closure's mutable borrow of delivered, so it can be printed afterwards.
25. Realistic example: a tiny test runner
Test cases are named Fn closures returning Result. The runner reports each one and a summary.
fn run_tests(tests: &[(&str, Box<dyn Fn() -> Result<(), String>>)]) {
let mut passed = 0;
for (name, test) in tests {
match test() {
Ok(()) => {
passed += 1;
println!("ok {name}");
}
Err(e) => println!("FAILED {name}: {e}"),
}
}
println!("{passed}/{} passed", tests.len());
}
fn assert_eq_msg<T: PartialEq + std::fmt::Debug>(actual: T, expected: T) -> Result<(), String> {
if actual == expected { Ok(()) } else { Err(format!("expected {:?}, got {:?}", expected, actual)) }
}
fn slugify(s: &str) -> String {
s.trim().to_lowercase().replace(' ', "-")
}
fn main() {
let tests: Vec<(&str, Box<dyn Fn() -> Result<(), String>>)> = vec![
("lowercases", Box::new(|| assert_eq_msg(slugify("Rust"), String::from("rust")))),
("replaces spaces", Box::new(|| assert_eq_msg(slugify("a b c"), String::from("a-b-c")))),
("trims", Box::new(|| assert_eq_msg(slugify(" x "), String::from("x")))),
("collapses double spaces", Box::new(|| assert_eq_msg(slugify("a b"), String::from("a-b")))),
];
run_tests(&tests);
}ok lowercases
ok replaces spaces
ok trims
FAILED collapses double spaces: expected "a-b", got "a--b"
3/4 passedThe last test fails on purpose: slugify doesn't collapse repeated spaces. Rust has a real built-in test framework, covered in Testing.
Summary
Fn: reads captures, callable many times.FnMut: changes captures, callable many times with&mut.FnOnce: may consume captures, callable once.- Every closure is
FnOnce; closures that don't consume areFnMut; closures that don't mutate areFn. - The body decides the trait.
moveonly decides how values are captured. - Accept the least demanding bound you can:
FnOnceif called once,FnMutif called repeatedly,Fnif called through a shared reference or from multiple threads. - Box closures (
Box<dyn Fn...>,Box<dyn FnOnce...>) to store different closures together or choose one at runtime. - Closures are plain values sized by their captures, so passing them generically has no runtime cost.
Next: Iterators.
