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

503: Closure as Argument

Difficulty: 2 Level: Beginner-Intermediate Pass behavior into functions โ€” the foundation of every iterator adapter, callback system, and functional API in Rust.

The Problem This Solves

Without higher-order functions, you write the same loop structure over and over with a tiny variation in the body: filter-and-collect, transform-and-sum, find-by-condition. Every new requirement means a new function. The logic structure is duplicated; only the behavior inside changes. The alternative in C is function pointers โ€” but they can't capture state without passing an explicit `void* userdata` parameter. In older Java you'd create an anonymous class implementing a single-method interface. Both approaches are verbose and error-prone. Rust's `F: Fn(T) -> U` generic bound gives you behavior-as-parameter with zero overhead: each call site gets its own monomorphized function, inlined at compile time. The closure carries its own state (captured variables) without needing a separate userdata parameter.

The Intuition

Passing a closure as an argument is like passing a recipe to a chef: the chef handles the kitchen infrastructure (iteration, error handling, threading), and you provide just the transformation to apply to each ingredient. In Python, `map(lambda x: x*2, items)` passes a lambda as a behavior. JavaScript uses arrow functions: `items.filter(x => x > 5)`. Rust's version is `items.iter().filter(|&x| x > 5)` โ€” identical concept, but the closure's type is checked at compile time and the call is inlined. The key choice is static dispatch (`F: Fn(...)` โ€” generic, zero-cost) versus dynamic dispatch (`&dyn Fn(...)` โ€” vtable, works for heterogeneous collections).

How It Works in Rust

// Static dispatch: compiler generates one version per closure type
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
 f(x)  // inlined at compile time โ€” zero overhead
}

// Works with any closure:
apply(|x| x * 2, 5);           // 10 โ€” captures nothing
let offset = 3;
apply(|x| x + offset, 5);      // 8 โ€” captures offset by ref

// Filter using a closure predicate
fn my_filter<T, F: Fn(&T) -> bool>(items: &[T], pred: F) -> Vec<&T> {
 items.iter().filter(|x| pred(x)).collect()
}
let nums = [1, 2, 3, 4, 5, 6];
let evens = my_filter(&nums, |x| *x % 2 == 0);  // [2, 4, 6]

// Dynamic dispatch: one function pointer, many closure types at runtime
fn apply_dyn(f: &dyn Fn(i32) -> i32, x: i32) -> i32 { f(x) }
// Use when storing different closures in a Vec or struct:
let ops: Vec<Box<dyn Fn(i32) -> i32>> = vec![
 Box::new(|x| x + 1),
 Box::new(|x| x * 2),
];
let result = ops.iter().fold(10, |acc, f| apply_dyn(f.as_ref(), acc));
// (10+1)*2 = 22

What This Unlocks

Key Differences

ConceptOCamlRust
HOF parameter`f : 'a -> 'b``f: F` where `F: Fn(A) -> B`
Static dispatchDefault (monomorphized functors)Generic `<F: Fn>` โ€” zero cost
Dynamic dispatchFirst-class functions (always boxed)`&dyn Fn` โ€” explicit vtable
Closure with stateCaptures env automaticallyCaptures env, compiler infers mode
Predicate in filter`List.filter pred xs``iter.filter(\x\pred(x))`
//! # 503. Closure as Argument
//! Higher-order functions that accept closures as parameters.

/// Apply a closure to a value (static dispatch โ€” zero cost)
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}

/// Apply a closure twice
fn apply_twice<T, F: Fn(T) -> T>(f: F, x: T) -> T {
    f(f(x))
}

/// Filter a slice with a predicate closure
fn my_filter<T, F: Fn(&T) -> bool>(items: &[T], pred: F) -> Vec<&T> {
    items.iter().filter(|x| pred(x)).collect()
}

/// Map with index โ€” passes (index, &item) to closure
fn mapi<T, U, F: Fn(usize, &T) -> U>(items: &[T], f: F) -> Vec<U> {
    items.iter().enumerate().map(|(i, x)| f(i, x)).collect()
}

/// Compose two functions: apply g then f
fn compose<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
where
    F: Fn(B) -> C,
    G: Fn(A) -> B,
{
    move |x| f(g(x))
}

/// Dynamic dispatch version โ€” accepts &dyn Fn for heterogeneous use
fn apply_dyn(f: &dyn Fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

/// Repeat a side-effecting closure N times with index
fn repeat_with<F: Fn(usize)>(f: F, n: usize) {
    (0..n).for_each(f);
}

fn main() {
    // Static dispatch
    println!("apply double 5 = {}", apply(|x| x * 2, 5));
    println!("apply_twice +3 on 10 = {}", apply_twice(|x| x + 3, 10));

    // Filter
    let nums = [1, 2, 3, 4, 5, 6];
    let evens: Vec<_> = my_filter(&nums, |x| *x % 2 == 0);
    println!("evens: {:?}", evens);

    // Map with index
    let words = ["hello", "world", "rust"];
    let indexed = mapi(&words, |i, w| format!("{}:{}", i, w));
    println!("indexed: {:?}", indexed);

    // Composition
    let double_then_inc = compose(|x: i32| x + 1, |x| x * 2);
    println!("double_then_inc(5) = {}", double_then_inc(5)); // 11

    // Dynamic dispatch
    let ops: Vec<Box<dyn Fn(i32) -> i32>> = vec![
        Box::new(|x| x + 1),
        Box::new(|x| x * 2),
        Box::new(|x| x - 3),
    ];
    let result = ops.iter().fold(10, |acc, f| apply_dyn(f.as_ref(), acc));
    println!("pipeline(10): {}", result); // (10+1)*2-3 = 19

    // Repeat with index
    print!("squares: ");
    repeat_with(|i| print!("{} ", i * i), 5);
    println!();
}

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

    #[test]
    fn test_apply() {
        assert_eq!(apply(|x| x * 3, 4), 12);
    }

    #[test]
    fn test_apply_twice() {
        assert_eq!(apply_twice(|x| x + 3, 10), 16);
    }

    #[test]
    fn test_my_filter() {
        let v = [1, 2, 3, 4, 5];
        let odds = my_filter(&v, |x| *x % 2 != 0);
        assert_eq!(odds, vec![&1, &3, &5]);
    }

    #[test]
    fn test_mapi() {
        let v = [10, 20, 30];
        let r = mapi(&v, |i, x| i as i32 * (*x));
        assert_eq!(r, vec![0, 20, 60]);
    }

    #[test]
    fn test_compose() {
        let f = compose(|x: i32| x + 1, |x| x * 2);
        assert_eq!(f(5), 11);
        assert_eq!(f(0), 1);
    }
}
(* Passing functions/closures as arguments in OCaml *)

(* Basic higher-order function *)
let apply f x = f x

(* Apply a function twice *)
let apply_twice f x = f (f x)

(* Filter a list with a predicate *)
let my_filter pred lst =
  List.fold_right (fun x acc -> if pred x then x :: acc else acc) lst []

(* Transform with index *)
let mapi f lst =
  let rec go i = function
    | [] -> []
    | x :: xs -> f i x :: go (i + 1) xs
  in
  go 0 lst

(* Compose two functions *)
let compose f g x = f (g x)

(* Run a side-effecting function n times with index *)
let repeat_with f n =
  for i = 0 to n - 1 do f i done

let () =
  Printf.printf "apply double 5 = %d\n" (apply (fun x -> x * 2) 5);
  Printf.printf "apply_twice +3 on 10 = %d\n" (apply_twice (fun x -> x + 3) 10);

  let evens = my_filter (fun x -> x mod 2 = 0) [1;2;3;4;5;6] in
  Printf.printf "evens: [%s]\n" (String.concat ";" (List.map string_of_int evens));

  let indexed = mapi (fun i x -> Printf.sprintf "%d:%d" i x) [10;20;30] in
  Printf.printf "indexed: [%s]\n" (String.concat ";" indexed);

  let double_then_inc = compose (fun x -> x + 1) (fun x -> x * 2) in
  Printf.printf "double_then_inc 5 = %d\n" (double_then_inc 5);

  print_string "squares: ";
  repeat_with (fun i -> Printf.printf "%d " (i * i)) 5;
  print_newline ()