ExamplesBy LevelBy TopicLearning Paths
502 Fundamental

Fn, FnMut, FnOnce

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Fn, FnMut, FnOnce" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Higher-order functions need to express constraints on how many times and in what way a callable can be invoked. Key difference from OCaml: 1. **Type

Tutorial

The Problem

Higher-order functions need to express constraints on how many times and in what way a callable can be invoked. A sort key function must be callable many times without side effects — Fn. A stateful counter must be called multiple times with mutation — FnMut. A function that consumes its captured data should only be called once — FnOnce. Without these distinctions, the type system could not prevent: calling a one-shot closure twice (use-after-move), forgetting to declare mutable access, or passing a mutable closure where immutable sharing is expected.

🎯 Learning Outcomes

  • • Bound function parameters with F: Fn(), F: FnMut(), F: FnOnce()
  • • Understand the hierarchy: Fn: FnMut: FnOnce (subtype relationship)
  • • Declare the call variable mut f for FnMut callers
  • • Write closures that implement each trait automatically
  • • Use dyn Fn/dyn FnMut/dyn FnOnce for trait objects
  • Code Example

    #![allow(clippy::all)]
    //! # Fn FnMut FnOnce — Closure Traits
    
    /// Fn: can be called multiple times, doesn't mutate state
    pub fn call_fn<F: Fn() -> i32>(f: F) -> i32 {
        f() + f()
    }
    
    /// FnMut: can be called multiple times, may mutate state
    pub fn call_fn_mut<F: FnMut() -> i32>(mut f: F) -> i32 {
        f() + f()
    }
    
    /// FnOnce: can only be called once, consumes captured values
    pub fn call_fn_once<F: FnOnce() -> String>(f: F) -> String {
        f()
    }
    
    pub fn example_fn() -> i32 {
        let x = 10;
        call_fn(|| x) // Borrows x immutably
    }
    
    pub fn example_fn_mut() -> i32 {
        let mut counter = 0;
        call_fn_mut(|| {
            counter += 1;
            counter
        }) // Mutably borrows counter
    }
    
    pub fn example_fn_once() -> String {
        let s = String::from("owned");
        call_fn_once(|| s) // Moves s
    }
    
    /// All closures implement FnOnce, some also implement FnMut, some also implement Fn
    pub fn closure_hierarchy() {
        let x = 10;
        let f: &dyn Fn() -> i32 = &|| x;
        let _: &dyn FnMut() -> i32 = f; // Fn implies FnMut
        let _: &dyn FnOnce() -> i32 = f; // FnMut implies FnOnce
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_fn() {
            assert_eq!(example_fn(), 20);
        }
    
        #[test]
        fn test_fn_mut() {
            assert_eq!(example_fn_mut(), 3); // 1 + 2
        }
    
        #[test]
        fn test_fn_once() {
            assert_eq!(example_fn_once(), "owned");
        }
    
        #[test]
        fn test_fn_pointer() {
            fn add_one(x: i32) -> i32 {
                x + 1
            }
            let fp: fn(i32) -> i32 = add_one;
            assert_eq!(fp(41), 42);
        }
    }

    Key Differences

  • Type-level enforcement: Rust's FnOnce prevents double-calling at compile time; OCaml must use runtime guards (ref bool) for the same constraint.
  • **mut f requirement**: Rust's call site must declare mut f for FnMut callers; OCaml has no such requirement.
  • Trait objects: Box<dyn Fn()>, Box<dyn FnMut()>, and Box<dyn FnOnce()> are distinct types in Rust; OCaml uses a single function type.
  • Subtyping: Rust's Fn: FnMut: FnOnce subtype coercions happen at compile time via trait coherence; OCaml has no equivalent.
  • OCaml Approach

    OCaml functions are first-class but have no trait hierarchy — all closures are uniformly applicable:

    let call_fn (f: unit -> int) = f () + f ()
    let call_fn_mut (f: unit -> int) = f () + f ()  (* same signature *)
    
    (* "FnOnce" semantics must be enforced manually *)
    let call_once f =
      let called = ref false in
      fun () -> if !called then failwith "called twice"
                else (called := true; f ())
    

    OCaml has no type-level distinction between a closure that mutates, one that doesn't, or one that should only be called once.

    Full Source

    #![allow(clippy::all)]
    //! # Fn FnMut FnOnce — Closure Traits
    
    /// Fn: can be called multiple times, doesn't mutate state
    pub fn call_fn<F: Fn() -> i32>(f: F) -> i32 {
        f() + f()
    }
    
    /// FnMut: can be called multiple times, may mutate state
    pub fn call_fn_mut<F: FnMut() -> i32>(mut f: F) -> i32 {
        f() + f()
    }
    
    /// FnOnce: can only be called once, consumes captured values
    pub fn call_fn_once<F: FnOnce() -> String>(f: F) -> String {
        f()
    }
    
    pub fn example_fn() -> i32 {
        let x = 10;
        call_fn(|| x) // Borrows x immutably
    }
    
    pub fn example_fn_mut() -> i32 {
        let mut counter = 0;
        call_fn_mut(|| {
            counter += 1;
            counter
        }) // Mutably borrows counter
    }
    
    pub fn example_fn_once() -> String {
        let s = String::from("owned");
        call_fn_once(|| s) // Moves s
    }
    
    /// All closures implement FnOnce, some also implement FnMut, some also implement Fn
    pub fn closure_hierarchy() {
        let x = 10;
        let f: &dyn Fn() -> i32 = &|| x;
        let _: &dyn FnMut() -> i32 = f; // Fn implies FnMut
        let _: &dyn FnOnce() -> i32 = f; // FnMut implies FnOnce
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_fn() {
            assert_eq!(example_fn(), 20);
        }
    
        #[test]
        fn test_fn_mut() {
            assert_eq!(example_fn_mut(), 3); // 1 + 2
        }
    
        #[test]
        fn test_fn_once() {
            assert_eq!(example_fn_once(), "owned");
        }
    
        #[test]
        fn test_fn_pointer() {
            fn add_one(x: i32) -> i32 {
                x + 1
            }
            let fp: fn(i32) -> i32 = add_one;
            assert_eq!(fp(41), 42);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_fn() {
            assert_eq!(example_fn(), 20);
        }
    
        #[test]
        fn test_fn_mut() {
            assert_eq!(example_fn_mut(), 3); // 1 + 2
        }
    
        #[test]
        fn test_fn_once() {
            assert_eq!(example_fn_once(), "owned");
        }
    
        #[test]
        fn test_fn_pointer() {
            fn add_one(x: i32) -> i32 {
                x + 1
            }
            let fp: fn(i32) -> i32 = add_one;
            assert_eq!(fp(41), 42);
        }
    }

    Deep Comparison

    Fn Fnmut Fnonce: Comparison

    See src/lib.rs for the Rust implementation.

    Exercises

  • Retry combinator: Write fn retry<F: FnMut() -> bool>(mut f: F, n: usize) -> bool that calls f up to n times and returns true on the first success.
  • **once adapter**: Write fn once<F: FnOnce() -> T, T>(f: F) -> impl FnMut() -> Option<T> that wraps a FnOnce in a FnMut that returns Some(result) on first call and None thereafter.
  • Trait object cost: Benchmark dyn Fn(i32)->i32 (dynamic dispatch via vtable) against impl Fn(i32)->i32 (monomorphised static dispatch) for 10 million calls using criterion.
  • Open Source Repos