Guide contents

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 FnOnce if you call it at most once. This accepts every closure.
  • Accept FnMut if you call it several times, one call at a time.
  • Accept Fn if you need to call it through a shared reference, or from several threads.

1. A closure that only reads is Fn

Rust
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");
}
Output
hi Ferris / hi Ferris
Ferris is still ours

2. A closure that changes state is FnMut

Rust
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);
}
Output
[101, 102, 103], next_id is now 103

The 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.

Rust✗ does not compile · E0594
fn call_twice(f: impl Fn()) {
    f();
    f();
}

fn main() {
    let mut count = 0;
    call_twice(|| count += 1);
    println!("{count}");
}
Output
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.

Rust
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));
}
Output
2 items

5. Calling an FnOnce closure twice

Rust✗ does not compile · E0382
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();
}
Output
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

Rust✗ does not compile · E0525
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);
}
Output
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.

Rust
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));
}
Output
4

8. 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.

Rust
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);
}
Output
1 7 1 1 guest

9. 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.

Rust
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);
}
Output
["$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.

Rust
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}");
}
Output
1024
longest: banana

11. Storing an FnMut in a struct

A struct holding a stateful closure must call it through &mut self.

Rust
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);
}
Output
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.

Rust
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());
    }
}
Output
emailed q3-report.pdf
deleted account ana@example.com
cleared cache

13. Returning different closures with Box<dyn Fn>

-> impl Fn must return one concrete closure. To choose between closures at runtime, box them.

Rust
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));
    }
}
Output
student  10000 -> 5000
bulk     10000 -> 9000
fixed    10000 -> 10500
regular  10000 -> 10000

14. 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.

Rust
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(&nothing));
    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());
}
Output
captures nothing: 0 bytes
moves a u64:      8 bytes
borrows a String: 8 bytes
moves a String:   24 bytes
42 2 4

A 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.

Rust
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"));
}
Output
HELLO-RUST-WORLD
42

16. Partial application and currying

A function can fix some arguments and return a closure taking the rest.

Rust
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);
}
Output
2000 1900
[13, 17]

17. Memoization with an FnMut closure

A closure can own a cache and fill it as it runs.

Rust
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");
}
Output
[9, 16, 9, 9, 16, 25]
computed 3 times for 6 calls

18. A closure that captures a closure

Closures are values, so they can capture other closures, which gives you higher-order building blocks.

Rust
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<_>>());
}
Output
[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.

Rust
#[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);
}
Output
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.

Rust
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));
}
Output
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.

Rust
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"));
}
Output
[log] GET /health?key=secret -> 200 ok
[log] GET /health -> 401
[log] GET /nope?key=secret -> 404

type 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.

Rust
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}");
}
Output
     ab: Err("failed: format")
  admin: Err("failed: reserved")
  taken: Err("failed: available")
 ferris: Ok(())
remote lookups: 2

23. 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.

Rust
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);
    }
}
Output
["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.

Rust
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);
}
Output
["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.

Rust
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);
}
Output
ok     lowercases
ok     replaces spaces
ok     trims
FAILED collapses double spaces: expected "a-b", got "a--b"
3/4 passed

The 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 are FnMut; closures that don't mutate are Fn.
  • The body decides the trait. move only decides how values are captured.
  • Accept the least demanding bound you can: FnOnce if called once, FnMut if called repeatedly, Fn if 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.