๐Ÿฆ€ Functional Rust
๐ŸŽฌ Closures in Rust Fn/FnMut/FnOnce, capturing environment, move closures, higher-order functions.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Closures capture variables from their environment โ€” by reference, mutable reference, or by value (move)

โ€ข Three traits: Fn (shared borrow), FnMut (mutable borrow), FnOnce (takes ownership)

โ€ข Higher-order functions like .map(), .filter(), .fold() accept closures as arguments

โ€ข move closures take ownership of captured variables โ€” essential for threading

โ€ข Closures enable functional patterns: partial application, composition, and strategy

516: Complex Closure Environments

Difficulty: 3 Level: Intermediate Closures that capture structs, collections, and other closures as their environment.

The Problem This Solves

In simple examples, closures capture one or two primitives. Real-world closures often capture much richer state: a configuration struct, a `HashMap` for lookup, a `Vec` with an index for cycling, or another closure to pass along. Understanding how Rust handles these complex captures โ€” including what gets moved versus borrowed, and how nested closures compose โ€” is essential for building formatters, pipelines, factories, and middleware chains. The question is always: what does this closure own? When you `move` a `Config` struct into a closure, the closure owns everything in that struct, including its `Box<dyn Fn>` field. When you capture a `HashMap` by move, the closure becomes a lookup function that owns its data. When a closure captures another closure, you get higher-order composition with no external state.

The Intuition

A closure's environment is like a small struct the compiler generates for you. Every captured variable becomes a field in that struct. With `move`, the fields contain owned values. Without `move`, they contain references. The closure is that struct plus a `call` method. When you write a closure that captures another closure, you're nesting structs โ€” composition by value. The outer closure's environment holds the inner closure, and so on. This is how pipeline stages, middleware chains, and transformer factories work in Rust.

How It Works in Rust

1. `move` for ownership โ€” `move |...| { ... }` transfers ownership of all captured variables into the closure; the original binding is consumed. 2. Complex struct capture โ€” capturing a `Config` (with a `Box<dyn Fn>` field) works fine; the closure owns the entire config, including its closure field. 3. `Vec` + index cycler โ€” capture a `Vec<T>` by move and a mutable `index: usize`; the `FnMut` closure increments the index on each call, wrapping via `%`. 4. Nested closure factory โ€” a closure returning a `Box<dyn Fn>` captures a local variable; the returned closure captures from the outer closure's environment. 5. Pipeline via captured `next` โ€” each step captures an optional `Box<dyn Fn(i32) -> i32>` as `next`; calling it chains to the next stage naturally.

What This Unlocks

Key Differences

ConceptOCamlRust
Closure captureBy reference (closures close over mutable refs)`move` for ownership; implicit borrow otherwise
Capturing a struct with closuresTransparent; GC managesClosure owns the struct; `Box<dyn Fn>` fields work fine
Higher-order closuresNatural; first-class functions`Box<dyn Fn>` or `impl Fn` for returning closures
Mutable captured state`ref` cells or mutable bindings`FnMut` + `move` captured `mut` variables
//! # 516. Complex Closure Environments
//! Closures capturing structs, collections, and other closures.

struct Config {
    prefix: String,
    max_len: usize,
    transform: Box<dyn Fn(String) -> String>,
}

/// Closure capturing a Config struct
fn make_formatter(cfg: Config) -> impl FnMut(&str) -> String {
    move |s: &str| {
        let truncated = if s.len() > cfg.max_len {
            format!("{}...", &s[..cfg.max_len])
        } else {
            s.to_string()
        };
        (cfg.transform)(format!("{}{}", cfg.prefix, truncated))
    }
}

/// Closure capturing a Vec and an index โ€” cyclic iterator
fn make_cycler<T: Clone>(items: Vec<T>) -> impl FnMut() -> T {
    let mut index = 0;
    move || {
        let val = items[index].clone();
        index = (index + 1) % items.len();
        val
    }
}

/// Nested closures: factory that creates specialized closures
fn make_multiplier_factory(base: i32) -> Box<dyn Fn(i32) -> Box<dyn Fn(i32) -> i32>> {
    Box::new(move |factor| {
        let combined = base * factor;
        Box::new(move |x| x * combined)
    })
}

/// Closure capturing another closure as part of its environment
fn make_pipeline_step(
    name: String,
    transform: impl Fn(i32) -> i32 + 'static,
    next: Option<Box<dyn Fn(i32) -> i32>>,
) -> impl Fn(i32) -> i32 {
    move |x| {
        let after_transform = transform(x);
        println!("  [{}]: {} -> {}", name, x, after_transform);
        match &next {
            Some(f) => f(after_transform),
            None => after_transform,
        }
    }
}

fn main() {
    // Complex struct capture
    let cfg = Config {
        prefix: "[INFO] ".to_string(),
        max_len: 10,
        transform: Box::new(|s| s.to_uppercase()),
    };
    let mut fmt = make_formatter(cfg);
    println!("{}", fmt("hello world this is a long message"));
    println!("{}", fmt("hi"));

    // Vec + index capture
    println!("\nCycler:");
    let mut cycle = make_cycler(vec!["red", "green", "blue"]);
    for _ in 0..7 {
        print!("{} ", cycle());
    }
    println!();

    // Nested closure factory
    let make_times = make_multiplier_factory(5);
    let times10 = make_times(2); // base=5, factor=2 => combined=10
    let times15 = make_times(3); // base=5, factor=3 => combined=15
    println!("\ntimes10(3) = {}", times10(3));
    println!("times15(4) = {}", times15(4));

    // Pipeline of closures, each capturing the previous
    println!("\nPipeline trace:");
    let step3 = make_pipeline_step("negate".to_string(), |x| -x, None);
    let step2 = make_pipeline_step("double".to_string(), |x| x * 2, Some(Box::new(step3)));
    let step1 = make_pipeline_step("add5".to_string(),  |x| x + 5, Some(Box::new(step2)));
    let result = step1(3);
    println!("Final: {}", result); // (3+5)*2 = 16, negated = -16

    // Closure over HashMap
    let mut scores: std::collections::HashMap<&str, i32> = std::collections::HashMap::new();
    scores.insert("alice", 90);
    scores.insert("bob", 85);
    let lookup = move |name: &str| scores.get(name).copied().unwrap_or(0);
    println!("\nalice: {}, carol: {}", lookup("alice"), lookup("carol"));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cycler() {
        let mut c = make_cycler(vec![1, 2, 3]);
        assert_eq!(c(), 1);
        assert_eq!(c(), 2);
        assert_eq!(c(), 3);
        assert_eq!(c(), 1); // wraps
    }

    #[test]
    fn test_multiplier_factory() {
        let factory = make_multiplier_factory(3);
        let times6 = factory(2);
        assert_eq!(times6(4), 24); // 4 * 6
    }

    #[test]
    fn test_formatter() {
        let cfg = Config {
            prefix: ">>".to_string(),
            max_len: 5,
            transform: Box::new(|s| s),
        };
        let mut fmt = make_formatter(cfg);
        assert_eq!(fmt("hi"), ">>hi");
        assert_eq!(fmt("hello world"), ">>hello...");
    }
}
(* Complex closure environments in OCaml *)

(* Closure capturing multiple fields *)
type config = {
  prefix: string;
  max_len: int;
  transform: string -> string;
}

let make_formatter cfg =
  fun s ->
    let truncated = if String.length s > cfg.max_len
      then String.sub s 0 cfg.max_len ^ "..."
      else s
    in
    cfg.transform (cfg.prefix ^ truncated)

(* Closure over a list and a counter *)
let make_cycler items =
  let arr = Array.of_list items in
  let i = ref 0 in
  fun () ->
    let v = arr.(!i) in
    i := (!i + 1) mod Array.length arr;
    v

(* Nested closure capturing outer environment *)
let make_multiplier_factory base =
  fun factor ->
    let combined = base * factor in
    fun x -> x * combined

let () =
  let fmt = make_formatter {
    prefix = "[INFO] ";
    max_len = 10;
    transform = String.uppercase_ascii;
  } in
  Printf.printf "%s\n" (fmt "hello world this is long");
  Printf.printf "%s\n" (fmt "hi");

  let cycle = make_cycler ["red"; "green"; "blue"] in
  for _ = 1 to 7 do
    Printf.printf "%s " (cycle ())
  done;
  print_newline ();

  let times10 = make_multiplier_factory 5 2 in
  Printf.printf "times10(3) = %d\n" (times10 3)