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

517: Closure-to-fn-pointer Coercion

Difficulty: 3 Level: Intermediate Non-capturing closures coerce to `fn` pointers; capturing ones cannot.

The Problem This Solves

Rust has two kinds of callable values: function pointers (`fn(T) -> U`) and closures (`impl Fn`). Function pointers are a single machine-word โ€” just an address. Closures are fat: they carry their captured environment. This size difference matters when you're building dispatch tables, FFI callbacks, or data structures that store many callbacks of the same type. If you use `Box<dyn Fn>` for everything, you pay for heap allocation and indirection even when the closure doesn't capture anything. If you want a `[fn(i32) -> i32; N]` array (all same size, stack-allocated), you need function pointers โ€” not `dyn Fn`. The coercion rule gives you this for free: any closure that captures nothing is automatically coercible to a `fn` pointer of the matching signature. Understanding this boundary โ€” what can and cannot coerce โ€” helps you choose the right type in APIs, avoid unnecessary allocations, and write ergonomic callback registration without forcing callers to use `Box`.

The Intuition

A non-capturing closure is just a function with an anonymous name. It has no environment to carry. So its type is a function pointer โ€” the compiler will let it coerce to one. A capturing closure, on the other hand, is a struct with a `call` method. It has no function-pointer representation because its environment goes with it everywhere. You must use `Box<dyn Fn>` or `impl Fn` for those. Think of it like this: if the closure needs a backpack (captured variables), it can't pretend to be a bare pointer. If it travels light, it can.

How It Works in Rust

1. Non-capturing closure โ€” `let f: fn(i32) -> i32 = |x| x + 1;` compiles; the closure captures nothing, so it coerces. 2. Named function โ€” `let f: fn(i32) -> i32 = double;` always works; named functions are `fn` pointers. 3. Array of fn pointers โ€” `[double, triple, |x| x * x]` works if all are non-capturing; uniform size, stack-allocated. 4. Capturing closure โ€” `let offset = 42; let f: fn(i32) -> i32 = |x| x + offset;` fails; must use `Box<dyn Fn(i32) -> i32>` instead. 5. FFI-style callbacks โ€” register a `fn(i32) -> i32` type alias as a callback; non-capturing closures and named functions both satisfy it without boxing.

What This Unlocks

Key Differences

ConceptOCamlRust
Function pointersAll functions/closures are values; no distinction`fn` pointer (no capture) vs closure (`impl Fn`/`Box<dyn Fn>`)
Closure coercionNo equivalent; all closures are uniformNon-capturing closures coerce to `fn T -> U`; capturing ones cannot
Array of functions`(int -> int) array` naturally`[fn(i32) -> i32; N]` requires non-capturing closures
Callback type`'a -> 'b` function typeChoose `fn`, `impl Fn`, or `Box<dyn Fn>` based on capture needs
//! # 517. Closure-to-fn-pointer Coercion
//! Non-capturing closures coerce to fn pointers; capturing ones cannot.

/// Accept a function pointer explicitly
fn apply_fn_ptr(f: fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

/// Named functions are fn pointers
fn double(x: i32) -> i32 { x * 2 }
fn triple(x: i32) -> i32 { x * 3 }
fn negate(x: i32) -> i32 { -x  }

/// Array of function pointers (all same size โ€” no fat pointers)
fn make_transform_table() -> [fn(i32) -> i32; 4] {
    [
        double,
        triple,
        negate,
        |x| x + 10, // non-capturing closure coerces to fn ptr
    ]
}

/// Function that stores fn pointers in a Vec
fn build_pipeline(ops: Vec<fn(i32) -> i32>) -> impl Fn(i32) -> i32 {
    move |x| ops.iter().fold(x, |acc, f| f(acc))
}

/// Demonstrate what CAN and CANNOT be coerced
fn coercion_examples() {
    // โœ… Non-capturing: coerces to fn(i32) -> i32
    let f1: fn(i32) -> i32 = |x| x + 1;
    println!("fn ptr: f1(5) = {}", f1(5));

    // โœ… Named function โ€” is a fn pointer
    let f2: fn(i32) -> i32 = double;
    println!("named fn: f2(5) = {}", f2(5));

    // โœ… Stored in array (all same size โ€” 1 word each)
    let table: [fn(i32) -> i32; 3] = [double, triple, |x| x * x];
    for (i, f) in table.iter().enumerate() {
        println!("  table[{}](4) = {}", i, f(4));
    }

    // โŒ Capturing closure CANNOT coerce to fn ptr
    let offset = 42;
    // This would NOT compile: let f3: fn(i32) -> i32 = |x| x + offset;
    // Must use impl Fn or Box<dyn Fn>:
    let f3: Box<dyn Fn(i32) -> i32> = Box::new(move |x| x + offset);
    println!("capturing (Box<dyn Fn>): f3(0) = {}", f3(0));
}

/// FFI-style: C functions expect fn pointers
/// (simulated โ€” not actual FFI here)
type Callback = fn(i32) -> i32;

fn register_callback(cb: Callback) -> Callback {
    cb // store/return it like a C callback
}

fn main() {
    println!("=== Named fn pointers ===");
    println!("apply_fn_ptr(double, 5) = {}", apply_fn_ptr(double, 5));
    println!("apply_fn_ptr(negate, 5) = {}", apply_fn_ptr(negate, 5));

    println!("\n=== Transform table ===");
    let table = make_transform_table();
    let names = ["double", "triple", "negate", "+10"];
    for (name, f) in names.iter().zip(table.iter()) {
        println!("{}: {}", name, f(7));
    }

    println!("\n=== Pipeline of fn ptrs ===");
    let pipeline = build_pipeline(vec![double, triple, negate]);
    println!("pipeline(3) = {}", pipeline(3)); // negate(triple(double(3))) = -18

    println!("\n=== Coercion examples ===");
    coercion_examples();

    println!("\n=== Callback registration ===");
    let cb = register_callback(|x| x * x);
    println!("callback(9) = {}", cb(9));
}

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

    #[test]
    fn test_fn_ptr_apply() {
        assert_eq!(apply_fn_ptr(double, 5), 10);
        assert_eq!(apply_fn_ptr(negate, 3), -3);
    }

    #[test]
    fn test_non_capturing_coercion() {
        let f: fn(i32) -> i32 = |x| x * x;
        assert_eq!(f(4), 16);
        assert_eq!(apply_fn_ptr(f, 5), 25);
    }

    #[test]
    fn test_transform_table() {
        let table = make_transform_table();
        assert_eq!(table[0](5), 10); // double
        assert_eq!(table[1](5), 15); // triple
        assert_eq!(table[2](5), -5); // negate
        assert_eq!(table[3](5), 15); // +10
    }

    #[test]
    fn test_pipeline() {
        let p = build_pipeline(vec![|x: i32| x + 1, |x| x * 2]);
        assert_eq!(p(3), 8); // (3+1)*2
    }
}
(* OCaml: no distinction between function pointers and closures *)
(* All functions are values with the same representation *)

let double x = x * 2
let triple x = x * 3
let negate x = -x

(* Array of functions (all same type) *)
let transforms = [| double; triple; negate; (fun x -> x + 10) |]

(* Higher-order that takes a raw function *)
let apply_all fns x =
  Array.map (fun f -> f x) fns

let () =
  let results = apply_all transforms 5 in
  Array.iter (fun r -> Printf.printf "%d " r) results;
  print_newline ();

  (* Closures and named functions are the same type *)
  let add5 = fun x -> x + 5 in  (* closure, same type as double *)
  Printf.printf "add5(10) = %d, double(10) = %d\n" (add5 10) (double 10)