๐Ÿฆ€ 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

518: Function Pointers vs Closures

Difficulty: 2 Level: Beginner-Intermediate Two kinds of callable in Rust โ€” know when to use each for maximum clarity and performance.

The Problem This Solves

You write a function that accepts a callback and use `fn(i32) -> i32` as the parameter type. A user passes a closure that captures a variable โ€” and gets a type error. Or you write `F: Fn(i32) -> i32` everywhere and then try to put function pointers in an array โ€” and hit size issues. Understanding when to use `fn` pointers versus `impl Fn`/`Box<dyn Fn>` closures is essential for writing ergonomic APIs. Getting it wrong either rejects valid inputs, adds unnecessary overhead, or prevents storing callables in arrays and const contexts. There's also a performance angle: `fn` pointers are one machine word and can be stored in plain arrays without boxing. Closures may be zero-sized or large depending on what they capture, and have different inlining characteristics.

The Intuition

A function pointer (`fn(i32) -> i32`) is just an address โ€” a number pointing to a compiled function. Like a C function pointer. It can't capture state because there's nowhere to store it. But it's `Copy`, tiny (one word), and works in const contexts. A closure is an anonymous struct that happens to implement `Fn`/`FnMut`/`FnOnce`. It can carry captured state (like an object with fields). Each closure is its own type. The size varies: a non-capturing closure may be zero bytes; a closure capturing a `Vec` might be 24 bytes. The key insight: every `fn` pointer implements `Fn`, `FnMut`, and `FnOnce`. So APIs using `impl Fn` accept both function pointers and closures. APIs using `fn(...)` reject closures with captures. In Python and JavaScript, all functions are objects on the heap โ€” there's no distinction. Rust's separation gives you the zero-cost option (`fn` ptr) when you don't need capture, and the powerful option (`impl Fn`) when you do.

How It Works in Rust

fn square(x: i32) -> i32 { x * x }
fn double(x: i32) -> i32 { x * 2 }

// fn pointer: one word, Copy, no captures
fn apply_fn_ptr(f: fn(i32) -> i32, x: i32) -> i32 { f(x) }
// Generic: accepts fn pointers AND closures (static dispatch)
fn apply_generic<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }

// fn pointer table: array of (name, function) pairs โ€” no boxing needed
let ops: Vec<(&str, fn(i32) -> i32)> = vec![
 ("square", square),
 ("double", double),
 ("negate", |x| -x),   // non-capturing closure COERCES to fn ptr
];
// Non-capturing closures automatically coerce to fn pointers!

// fn pointer is Copy โ€” can copy freely
let f: fn(i32) -> i32 = square;
let g = f;    // copied
println!("{} {}", f(3), g(3)); // both work

// Closure with capture: ONLY works with impl Fn, NOT fn ptr
let offset = 100;
let add_offset = move |x: i32| x + offset;  // captures offset
// apply_fn_ptr(add_offset, 5);  // โœ— ERROR: can't coerce capturing closure to fn ptr
println!("{}", apply_generic(add_offset, 5)); // โœ“ 105

// Size comparison
println!("{}", std::mem::size_of::<fn(i32) -> i32>());  // 8 bytes (pointer)
let nc = |x: i32| x + 1;
println!("{}", std::mem::size_of_val(&nc));  // 0 bytes! (non-capturing = no state)
let cap = move |x: i32| x + offset;
println!("{}", std::mem::size_of_val(&cap)); // 4 bytes (captures one i32)

What This Unlocks

Key Differences

ConceptOCamlRust
Function pointer`int -> int` (first class, boxed by GC)`fn(i32) -> i32` โ€” one word, stack
ClosureSame type as fn โ€” no distinctionAnonymous type implementing `Fn*`
Can captureAlways`fn` ptr: no; closure: yes
Size1 word (uniform representation)`fn` ptr: 1 word; closure: 0..N bytes
Copy semanticsYes (value semantics)`fn` ptr: Copy; closure: depends on captures
//! # 518. Function Pointers vs Closures
//! Comparing fn pointers and closures: size, capabilities, use cases.

/// Named functions โ€” can be used as fn pointers
fn square(x: i32) -> i32 { x * x }
fn cube(x: i32) -> i32 { x * x * x }
fn double(x: i32) -> i32 { x * 2 }

/// Accepts fn pointer โ€” only non-capturing callables
fn apply_fn_ptr(f: fn(i32) -> i32, x: i32) -> i32 { f(x) }

/// Accepts any Fn โ€” works with both fn ptrs and closures
fn apply_generic<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }

/// Table using fn pointers (array โ€” constant size, stack allocated)
// fn pointer arrays can be indexed at compile time
type MathEntry = (&'static str, fn(i32) -> i32);

fn math_ops() -> Vec<(&'static str, fn(i32) -> i32)> {
    vec![
        ("square", square),
        ("cube",   cube),
        ("double", double),
        ("negate", |x| -x), // non-capturing closure coerces to fn ptr
    ]
}

/// Size comparison
fn show_sizes() {
    println!("Size of fn(i32)->i32: {} bytes", std::mem::size_of::<fn(i32) -> i32>());

    // Non-capturing closure: zero size (or same as fn ptr โ€” compiler-dependent)
    let nc_closure = |x: i32| x + 1;
    println!("Non-capturing closure size: {} bytes", std::mem::size_of_val(&nc_closure));

    // Capturing closure: contains captured value
    let offset = 42i32;
    let cap_closure = move |x: i32| x + offset;
    println!("Capturing closure (i32) size: {} bytes", std::mem::size_of_val(&cap_closure));

    let big_data = vec![0u8; 100];
    let cap_big = move |_: i32| big_data.len() as i32;
    println!("Capturing closure (Vec) size: {} bytes", std::mem::size_of_val(&cap_big));
}

fn main() {
    println!("=== Function pointer table ===");
    for (name, f) in math_ops() {
        println!("{}(5) = {}", name, f(5));
    }

    println!("\n=== fn ptr is Copy ===");
    let f: fn(i32) -> i32 = square;
    let g = f; // copied โ€” both usable
    println!("f(3) = {}, g(3) = {}", f(3), g(3));

    println!("\n=== apply_fn_ptr vs apply_generic ===");
    println!("fn ptr: {}", apply_fn_ptr(double, 7));
    println!("generic (fn ptr): {}", apply_generic(double, 7));
    // Closure captures โ€” only works with generic:
    let offset = 100;
    let add_offset = move |x: i32| x + offset;
    // apply_fn_ptr(add_offset, 5) // ERROR: captures
    println!("generic (closure): {}", apply_generic(add_offset, 7));

    println!("\n=== Size comparison ===");
    show_sizes();

    println!("\n=== Dynamic dispatch ===");
    // Vec of fn pointers โ€” all same type, no boxing needed
    let ops: Vec<fn(i32) -> i32> = vec![square, cube, double];
    for (i, op) in ops.iter().enumerate() {
        println!("  ops[{}](4) = {}", i, op(4));
    }
}

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

    #[test]
    fn test_fn_ptr_basic() {
        let f: fn(i32) -> i32 = square;
        assert_eq!(f(4), 16);
        assert_eq!(apply_fn_ptr(f, 3), 9);
    }

    #[test]
    fn test_fn_ptr_is_copy() {
        let f: fn(i32) -> i32 = cube;
        let g = f; // Copy
        assert_eq!(f(2), g(2));
    }

    #[test]
    fn test_generic_accepts_both() {
        // fn pointer
        assert_eq!(apply_generic(square, 5), 25);
        // closure
        let n = 10;
        assert_eq!(apply_generic(move |x| x + n, 5), 15);
    }

    #[test]
    fn test_math_table() {
        let ops = math_ops();
        let sq = ops.iter().find(|(name, _)| *name == "square").unwrap().1;
        assert_eq!(sq(6), 36);
    }
}
(* OCaml: no explicit distinction โ€” all are first-class function values *)

let square x = x * x
let cube   x = x * x * x

(* Higher-order *)
let apply f x = f x
let compose f g x = f (g x)

(* "Table" of transforms *)
let math_ops = [
  ("square", square);
  ("cube",   cube);
  ("double", fun x -> x * 2);
  ("negate", fun x -> -x);
]

let () =
  List.iter (fun (name, f) ->
    Printf.printf "%s(5) = %d\n" name (f 5)
  ) math_ops;

  (* Functions as values โ€” can be passed, returned, stored *)
  let pick_op name =
    List.assoc name math_ops
  in
  let op = pick_op "cube" in
  Printf.printf "picked cube(4) = %d\n" (op 4)