ExamplesBy LevelBy TopicLearning Paths
120 Intermediate

Closure Types: Fn, FnMut, FnOnce

Functional Programming

Tutorial

The Problem

Closures that capture their environment behave differently depending on what they do with captured values — read them, mutate them, or consume them. Rust encodes this in three traits: Fn (shared borrow), FnMut (mutable borrow), FnOnce (takes ownership). The hierarchy Fn ⊆ FnMut ⊆ FnOnce means the compiler selects the most restrictive trait that still allows the closure to be called correctly, preventing data races and use-after-move bugs at compile time.

🎯 Learning Outcomes

  • • Understand the three closure traits and when each is inferred
  • • Learn how the trait hierarchy (Fn implies FnMut implies FnOnce) affects function bounds
  • • See why FnOnce closures can only be called once and how the type system enforces this
  • • Practice writing higher-order functions that accept each closure kind
  • Code Example

    // Fn: closure captures `prefix` by move but only reads it
    pub fn make_greeter(prefix: String) -> impl Fn(&str) -> String {
        move |name| format!("{prefix}, {name}!")
    }
    
    // Can be called any number of times — no mutation, no consumption
    let greet = make_greeter("Hello".into());
    assert_eq!(greet("Alice"), "Hello, Alice!");
    assert_eq!(greet("Bob"),   "Hello, Bob!");

    Key Differences

  • Trait stratification: Rust distinguishes three calling conventions at the type level; OCaml has one uniform closure type.
  • Mutation safety: Rust's FnMut bound signals that a closure mutates state, preventing concurrent sharing; OCaml relies on the programmer to avoid unsafe concurrent mutation.
  • Consume on call: FnOnce enforces single-call semantics in the type system; OCaml has no equivalent — a closure returning a consumed value would panic at runtime, not compile time.
  • Inference: Rust infers the tightest trait that fits; OCaml infers a single function type 'a -> 'b.
  • OCaml Approach

    OCaml functions always implement the equivalent of Fn — closures in OCaml capture variables by reference to a heap-allocated environment and can be called any number of times. There is no distinction between Fn, FnMut, and FnOnce because OCaml's GC manages the environment and mutation is controlled separately via ref cells.

    Full Source

    #![allow(clippy::all)]
    // Example 120: Fn, FnMut, FnOnce
    //
    // Rust closures implement one or more of three traits based on how they
    // use captured variables:
    //   Fn      — borrows immutably; callable any number of times
    //   FnMut   — borrows mutably;  callable any number of times
    //   FnOnce  — moves out of captures; callable exactly once
    //
    // Every Fn is also FnMut and FnOnce.
    // Every FnMut is also FnOnce.
    // FnOnce is the most general bound; Fn is the most restrictive.
    
    // ---------------------------------------------------------------------------
    // Approach 1: Fn — immutable capture, callable repeatedly
    // ---------------------------------------------------------------------------
    
    /// Returns a closure that prepends `prefix` to any name.
    /// The closure only *reads* `prefix`, so it implements `Fn`.
    pub fn make_greeter(prefix: String) -> impl Fn(&str) -> String {
        move |name| format!("{prefix}, {name}!")
    }
    
    /// Accepts any `Fn` — caller knows it may be invoked multiple times
    /// with shared (non-mutable) access to captures.
    pub fn apply_twice<F: Fn() -> String>(f: F) -> (String, String) {
        (f(), f())
    }
    
    // ---------------------------------------------------------------------------
    // Approach 2: FnMut — mutable capture, callable repeatedly
    // ---------------------------------------------------------------------------
    
    /// Returns a counter closure.  It mutates its captured `count`, so the
    /// compiler infers `FnMut` (not plain `Fn`).
    pub fn make_counter() -> impl FnMut() -> u32 {
        let mut count = 0u32;
        move || {
            count += 1;
            count
        }
    }
    
    /// Calls an `FnMut` closure `n` times and collects the results.
    pub fn call_n_times<F: FnMut() -> u32>(mut f: F, n: usize) -> Vec<u32> {
        (0..n).map(|_| f()).collect()
    }
    
    // ---------------------------------------------------------------------------
    // Approach 3: FnOnce — moves captured value out, callable exactly once
    // ---------------------------------------------------------------------------
    
    /// Returns a closure that *consumes* `message` when called.
    /// Moving a value out of the closure body forces `FnOnce`.
    pub fn make_one_shot(message: String) -> impl FnOnce() -> String {
        move || message // `message` is moved out on the single call
    }
    
    /// Accepts an `FnOnce` — the type system enforces single invocation.
    pub fn consume_once<F: FnOnce() -> String>(f: F) -> String {
        f()
    }
    
    // ---------------------------------------------------------------------------
    // Approach 4: Higher-order functions showing the trait hierarchy
    // ---------------------------------------------------------------------------
    
    /// Accepts the *most restrictive* bound: the closure must be freely
    /// shareable (Fn).  This is appropriate for functions like `Iterator::map`.
    pub fn transform_all<F: Fn(i32) -> i32>(data: &[i32], f: F) -> Vec<i32> {
        data.iter().map(|&x| f(x)).collect()
    }
    
    /// Accepts `FnMut` — the closure may carry mutable state across calls.
    /// Useful for functions like `Iterator::for_each` that fold side effects.
    pub fn accumulate<F: FnMut(i32) -> i32>(data: &[i32], mut f: F) -> Vec<i32> {
        data.iter().map(|&x| f(x)).collect()
    }
    
    // ---------------------------------------------------------------------------
    // Tests
    // ---------------------------------------------------------------------------
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        // --- Fn ---
    
        #[test]
        fn test_fn_greeter_callable_multiple_times() {
            let greet = make_greeter("Hello".into());
            assert_eq!(greet("Alice"), "Hello, Alice!");
            assert_eq!(greet("Bob"), "Hello, Bob!");
            // A third call proves the closure is not consumed
            assert_eq!(greet("Charlie"), "Hello, Charlie!");
        }
    
        #[test]
        fn test_fn_apply_twice() {
            let label = "ping";
            // Pure Fn closure: only reads captured `label`
            let (a, b) = apply_twice(|| label.to_string());
            assert_eq!(a, "ping");
            assert_eq!(b, "ping");
        }
    
        #[test]
        fn test_fn_transform_all() {
            let doubled = transform_all(&[1, 2, 3, 4], |x| x * 2);
            assert_eq!(doubled, vec![2, 4, 6, 8]);
        }
    
        #[test]
        fn test_fn_transform_all_with_offset() {
            let offset = 10;
            // Closure captures `offset` by reference; still implements Fn
            let result = transform_all(&[1, 2, 3], |x| x + offset);
            assert_eq!(result, vec![11, 12, 13]);
        }
    
        // --- FnMut ---
    
        #[test]
        fn test_fnmut_counter_increments() {
            let mut counter = make_counter();
            assert_eq!(counter(), 1);
            assert_eq!(counter(), 2);
            assert_eq!(counter(), 3);
        }
    
        #[test]
        fn test_fnmut_call_n_times() {
            let counter = make_counter();
            let results = call_n_times(counter, 5);
            assert_eq!(results, vec![1, 2, 3, 4, 5]);
        }
    
        #[test]
        fn test_fnmut_accumulate_running_sum() {
            let mut running = 0i32;
            // Closure mutates `running` across calls — FnMut
            let result = accumulate(&[1, 2, 3, 4], |x| {
                running += x;
                running
            });
            assert_eq!(result, vec![1, 3, 6, 10]);
        }
    
        // --- FnOnce ---
    
        #[test]
        fn test_fnonce_consume_once() {
            let shot = make_one_shot("boom".into());
            let result = consume_once(shot);
            assert_eq!(result, "boom");
            // `shot` is moved into `consume_once`; calling it again would be a compile error
        }
    
        #[test]
        fn test_fnonce_inline_closure() {
            let owned = String::from("consumed");
            // Moving `owned` out inside the body makes this FnOnce
            let f: Box<dyn FnOnce() -> usize> = Box::new(move || owned.len());
            assert_eq!(f(), 8);
        }
    
        // --- Trait hierarchy ---
    
        #[test]
        fn test_fn_satisfies_fnmut_and_fnonce_bounds() {
            // A pure Fn closure can be passed where FnMut or FnOnce is required
            let addend = 5i32;
            let pure_fn = |x: i32| x + addend; // implements Fn
    
            // Works as FnMut
            let as_fnmut: Vec<i32> = accumulate(&[1, 2, 3], pure_fn);
            assert_eq!(as_fnmut, vec![6, 7, 8]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        // --- Fn ---
    
        #[test]
        fn test_fn_greeter_callable_multiple_times() {
            let greet = make_greeter("Hello".into());
            assert_eq!(greet("Alice"), "Hello, Alice!");
            assert_eq!(greet("Bob"), "Hello, Bob!");
            // A third call proves the closure is not consumed
            assert_eq!(greet("Charlie"), "Hello, Charlie!");
        }
    
        #[test]
        fn test_fn_apply_twice() {
            let label = "ping";
            // Pure Fn closure: only reads captured `label`
            let (a, b) = apply_twice(|| label.to_string());
            assert_eq!(a, "ping");
            assert_eq!(b, "ping");
        }
    
        #[test]
        fn test_fn_transform_all() {
            let doubled = transform_all(&[1, 2, 3, 4], |x| x * 2);
            assert_eq!(doubled, vec![2, 4, 6, 8]);
        }
    
        #[test]
        fn test_fn_transform_all_with_offset() {
            let offset = 10;
            // Closure captures `offset` by reference; still implements Fn
            let result = transform_all(&[1, 2, 3], |x| x + offset);
            assert_eq!(result, vec![11, 12, 13]);
        }
    
        // --- FnMut ---
    
        #[test]
        fn test_fnmut_counter_increments() {
            let mut counter = make_counter();
            assert_eq!(counter(), 1);
            assert_eq!(counter(), 2);
            assert_eq!(counter(), 3);
        }
    
        #[test]
        fn test_fnmut_call_n_times() {
            let counter = make_counter();
            let results = call_n_times(counter, 5);
            assert_eq!(results, vec![1, 2, 3, 4, 5]);
        }
    
        #[test]
        fn test_fnmut_accumulate_running_sum() {
            let mut running = 0i32;
            // Closure mutates `running` across calls — FnMut
            let result = accumulate(&[1, 2, 3, 4], |x| {
                running += x;
                running
            });
            assert_eq!(result, vec![1, 3, 6, 10]);
        }
    
        // --- FnOnce ---
    
        #[test]
        fn test_fnonce_consume_once() {
            let shot = make_one_shot("boom".into());
            let result = consume_once(shot);
            assert_eq!(result, "boom");
            // `shot` is moved into `consume_once`; calling it again would be a compile error
        }
    
        #[test]
        fn test_fnonce_inline_closure() {
            let owned = String::from("consumed");
            // Moving `owned` out inside the body makes this FnOnce
            let f: Box<dyn FnOnce() -> usize> = Box::new(move || owned.len());
            assert_eq!(f(), 8);
        }
    
        // --- Trait hierarchy ---
    
        #[test]
        fn test_fn_satisfies_fnmut_and_fnonce_bounds() {
            // A pure Fn closure can be passed where FnMut or FnOnce is required
            let addend = 5i32;
            let pure_fn = |x: i32| x + addend; // implements Fn
    
            // Works as FnMut
            let as_fnmut: Vec<i32> = accumulate(&[1, 2, 3], pure_fn);
            assert_eq!(as_fnmut, vec![6, 7, 8]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Fn, FnMut, FnOnce

    Side-by-Side Code

    OCaml

    (* OCaml has a single closure type — captures are implicit references *)
    let make_greeter prefix =
      fun name -> prefix ^ ", " ^ name ^ "!"
    
    let make_counter () =
      let count = ref 0 in
      fun () -> incr count; !count
    
    (* OCaml cannot express "callable exactly once" in its type system *)
    let one_shot msg = fun () -> msg
    

    Rust (idiomatic — Fn, read-only capture)

    // Fn: closure captures `prefix` by move but only reads it
    pub fn make_greeter(prefix: String) -> impl Fn(&str) -> String {
        move |name| format!("{prefix}, {name}!")
    }
    
    // Can be called any number of times — no mutation, no consumption
    let greet = make_greeter("Hello".into());
    assert_eq!(greet("Alice"), "Hello, Alice!");
    assert_eq!(greet("Bob"),   "Hello, Bob!");
    

    Rust (FnMut — mutable capture)

    // FnMut: closure mutates `count` each time it is called
    pub fn make_counter() -> impl FnMut() -> u32 {
        let mut count = 0u32;
        move || { count += 1; count }
    }
    
    let mut next = make_counter();
    assert_eq!(next(), 1);
    assert_eq!(next(), 2);
    

    Rust (FnOnce — consuming capture)

    // FnOnce: closure moves `message` out on the single call
    pub fn make_one_shot(message: String) -> impl FnOnce() -> String {
        move || message   // `message` is consumed here — cannot call again
    }
    
    let shot = make_one_shot("boom".into());
    assert_eq!(shot(), "boom");
    // shot() again → compile error: value used after move
    

    Type Signatures

    ConceptOCamlRust
    Read-only closure'a -> 'b (implicit)impl Fn(A) -> B
    Mutable-state closureunit -> 'a with ref insideimpl FnMut() -> A
    One-shot closurenot expressibleimpl FnOnce() -> A
    Higher-order read('a -> 'b) -> 'a -> 'bfn apply<F: Fn(A) -> B>(f: F, x: A) -> B
    Higher-order mutable(unit -> 'a) -> 'a listfn call_n<F: FnMut() -> A>(f: F, n: usize) -> Vec<A>

    Key Insights

  • Trait hierarchy mirrors capability: Fn ⊆ FnMut ⊆ FnOnce. Every Fn is automatically FnMut and FnOnce; the compiler picks the tightest trait automatically.
  • **OCaml has no equivalent of FnOnce**: In OCaml every closure can be called any number of times. Rust makes single-use semantics explicit and compiler-enforced, which matters for consuming resources (file handles, channels, owned data).
  • Ownership drives the inference: The compiler inspects what the closure does with its captures. Mere reads → Fn. Mutation via &mut → at least FnMut. Moving a value out of the body → only FnOnce. No annotation required — the traits are inferred automatically.
  • Higher-order function contracts become precise: Writing fn apply(f: impl Fn()) tells callers the function will call f repeatedly and safely. Writing fn run(f: impl FnOnce()) guarantees f is called at most once — an API-level promise enforced by the type system.
  • **move keyword transfers ownership into the closure**: OCaml closures always close over references. Rust's move keyword forces all captured variables to be owned by the closure, which enables returning closures from functions and sending them across threads (move || … is Send if the captured values are Send).
  • When to Use Each Style

    **Use Fn** when you need to call the closure many times without state change — e.g., Iterator::map, callbacks, pure transformations.

    **Use FnMut** when the closure accumulates state between calls — e.g., counters, folds with side effects, event handlers.

    **Use FnOnce** when the closure must consume its captures — e.g., spawning a thread with owned data, one-time initialization, RAII teardown callbacks.

    **Use move closures** whenever the closure outlives the current stack frame — returning closures from functions, spawning tasks, or storing closures in data structures.

    Exercises

  • Write a function apply_once<F: FnOnce() -> String>(f: F) -> String and verify that calling f twice causes a compile error.
  • Implement a memoizing wrapper: memoize<F: FnMut(u32) -> u32>(f: F) -> impl FnMut(u32) -> u32 that caches results.
  • Create a pipeline combinator that takes a Vec<Box<dyn FnMut(i32) -> i32>> and applies each closure in sequence.
  • Open Source Repos