ExamplesBy LevelBy TopicLearning Paths
504 Intermediate

Closure as Return

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Closure as Return" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. A function that returns a specialised function is a closure factory. Key difference from OCaml: 1. **`impl Fn` vs. `Box<dyn Fn>`**: Rust's `impl Fn` is zero

Tutorial

The Problem

A function that returns a specialised function is a closure factory. make_adder(5) returns a function that adds 5 to its argument — a specific case of partial application. make_counter() returns a stateful function that increments a counter on each call. These patterns are everywhere: middleware builders, parser combinators, event handlers, configuration-driven pipelines. Rust requires the return type to be concrete: impl Fn(i32) -> i32 for static dispatch (inlined by the compiler) or Box<dyn Fn(i32) -> i32> when type erasure is needed for heterogeneous collections.

🎯 Learning Outcomes

  • • Return closures with impl Fn(A) -> B for zero-cost static dispatch
  • • Return stateful closures with impl FnMut() -> T for generator patterns
  • • Use Box<dyn Fn> when the concrete type must be hidden or stored heterogeneously
  • • Capture multiple values in a returned closure with move
  • • Understand that impl Trait in return position infers one concrete type per call site
  • Code Example

    #![allow(clippy::all)]
    //! # Closure as Return — Returning Closures
    
    /// Return closure with impl Trait
    pub fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
        move |x| x + n
    }
    
    /// Return closure that captures multiple values
    pub fn make_linear(slope: f64, intercept: f64) -> impl Fn(f64) -> f64 {
        move |x| slope * x + intercept
    }
    
    /// Return closure that maintains state (using RefCell)
    pub fn make_counter() -> impl FnMut() -> i32 {
        let mut count = 0;
        move || {
            count += 1;
            count
        }
    }
    
    /// Return closure factory
    pub fn make_multiplier_factory() -> impl Fn(i32) -> Box<dyn Fn(i32) -> i32> {
        |factor| Box::new(move |x| x * factor)
    }
    
    /// Generic closure return
    pub fn make_mapper<T, F: Fn(T) -> T + 'static>(f: F) -> Box<dyn Fn(T) -> T>
    where
        T: 'static,
    {
        Box::new(f)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_make_adder() {
            let add5 = make_adder(5);
            assert_eq!(add5(10), 15);
            assert_eq!(add5(0), 5);
        }
    
        #[test]
        fn test_make_linear() {
            let f = make_linear(2.0, 1.0); // y = 2x + 1
            assert_eq!(f(3.0), 7.0);
        }
    
        #[test]
        fn test_counter() {
            let mut counter = make_counter();
            assert_eq!(counter(), 1);
            assert_eq!(counter(), 2);
            assert_eq!(counter(), 3);
        }
    
        #[test]
        fn test_factory() {
            let factory = make_multiplier_factory();
            let times3 = factory(3);
            let times5 = factory(5);
            assert_eq!(times3(10), 30);
            assert_eq!(times5(10), 50);
        }
    
        #[test]
        fn test_generic_mapper() {
            let double = make_mapper(|x: i32| x * 2);
            assert_eq!(double(21), 42);
        }
    }

    Key Differences

  • **impl Fn vs. Box<dyn Fn>**: Rust's impl Fn is zero-cost (monomorphised); Box<dyn Fn> adds a heap allocation and vtable dispatch. OCaml has uniform representation — no such distinction.
  • **FnMut return**: Rust's make_counter returns impl FnMut() — callers must declare mut counter. OCaml's counter closure mutates via ref without any declaration.
  • Lifetime of captures: Rust's impl Fn return's lifetime is tied to the closure's captures (the compiler infers this); OCaml's GC manages all lifetimes.
  • Type inference: Rust's compiler infers the concrete closure type behind impl Fn; OCaml infers the function type directly.
  • OCaml Approach

    OCaml functions naturally return closures — no special syntax required:

    let make_adder n = fun x -> x + n
    let make_linear slope intercept = fun x -> slope *. x +. intercept
    let make_counter () =
      let count = ref 0 in
      fun () -> incr count; !count
    

    OCaml's ref provides the mutable state for stateful closures; there is no mut annotation on the returned function.

    Full Source

    #![allow(clippy::all)]
    //! # Closure as Return — Returning Closures
    
    /// Return closure with impl Trait
    pub fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
        move |x| x + n
    }
    
    /// Return closure that captures multiple values
    pub fn make_linear(slope: f64, intercept: f64) -> impl Fn(f64) -> f64 {
        move |x| slope * x + intercept
    }
    
    /// Return closure that maintains state (using RefCell)
    pub fn make_counter() -> impl FnMut() -> i32 {
        let mut count = 0;
        move || {
            count += 1;
            count
        }
    }
    
    /// Return closure factory
    pub fn make_multiplier_factory() -> impl Fn(i32) -> Box<dyn Fn(i32) -> i32> {
        |factor| Box::new(move |x| x * factor)
    }
    
    /// Generic closure return
    pub fn make_mapper<T, F: Fn(T) -> T + 'static>(f: F) -> Box<dyn Fn(T) -> T>
    where
        T: 'static,
    {
        Box::new(f)
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_make_adder() {
            let add5 = make_adder(5);
            assert_eq!(add5(10), 15);
            assert_eq!(add5(0), 5);
        }
    
        #[test]
        fn test_make_linear() {
            let f = make_linear(2.0, 1.0); // y = 2x + 1
            assert_eq!(f(3.0), 7.0);
        }
    
        #[test]
        fn test_counter() {
            let mut counter = make_counter();
            assert_eq!(counter(), 1);
            assert_eq!(counter(), 2);
            assert_eq!(counter(), 3);
        }
    
        #[test]
        fn test_factory() {
            let factory = make_multiplier_factory();
            let times3 = factory(3);
            let times5 = factory(5);
            assert_eq!(times3(10), 30);
            assert_eq!(times5(10), 50);
        }
    
        #[test]
        fn test_generic_mapper() {
            let double = make_mapper(|x: i32| x * 2);
            assert_eq!(double(21), 42);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_make_adder() {
            let add5 = make_adder(5);
            assert_eq!(add5(10), 15);
            assert_eq!(add5(0), 5);
        }
    
        #[test]
        fn test_make_linear() {
            let f = make_linear(2.0, 1.0); // y = 2x + 1
            assert_eq!(f(3.0), 7.0);
        }
    
        #[test]
        fn test_counter() {
            let mut counter = make_counter();
            assert_eq!(counter(), 1);
            assert_eq!(counter(), 2);
            assert_eq!(counter(), 3);
        }
    
        #[test]
        fn test_factory() {
            let factory = make_multiplier_factory();
            let times3 = factory(3);
            let times5 = factory(5);
            assert_eq!(times3(10), 30);
            assert_eq!(times5(10), 50);
        }
    
        #[test]
        fn test_generic_mapper() {
            let double = make_mapper(|x: i32| x * 2);
            assert_eq!(double(21), 42);
        }
    }

    Deep Comparison

    Closure As Return: Comparison

    See src/lib.rs for the Rust implementation.

    Exercises

  • Fibonacci generator: Write fn make_fib() -> impl FnMut() -> u64 that returns successive Fibonacci numbers on each call, maintaining (a, b) state.
  • Rate limiter: Write fn make_rate_limiter(max_per_sec: u32) -> impl FnMut() -> bool using std::time::Instant that returns true when the call is within the rate limit and false when exceeded.
  • Typed pipeline builder: Write fn make_pipeline<A, B, C>(f: impl Fn(A)->B, g: impl Fn(B)->C) -> impl Fn(A)->C that chains two transformations and verify it composes with a third.
  • Open Source Repos