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

530: Closures in Benchmarking

Difficulty: 2 Level: Beginner-Intermediate Use closures to wrap workloads for repeatable measurement โ€” and `black_box` to keep the compiler honest.

The Problem This Solves

Writing a benchmark is easy. Writing a correct benchmark is surprisingly hard. The most common mistake: timing code that the compiler has silently deleted. LLVM sees that your computation's result is never used, concludes the entire computation is dead code, and removes it. Your benchmark reports sub-nanosecond timings for "doing nothing" โ€” and you publish those numbers. The second most common mistake: running the workload once and reporting that single measurement. One sample has no statistical validity. Cache effects, OS scheduling, CPU throttling, and branch predictor state all introduce variance that a single measurement cannot distinguish from signal. Closures solve both problems elegantly. A closure wraps the workload into a repeatable unit the harness can call N times. `std::hint::black_box` wraps the inputs and outputs, creating an opaque barrier the compiler cannot see through โ€” it cannot prove the result is unused, so it cannot delete the computation. Combined with warmup iterations, you get a statistically sound measurement with minimal boilerplate.

The Intuition

A benchmark closure is a promise: "here is a piece of work; run it as many times as you need." The harness holds the closure, calls it repeatedly with a timer around the loop, and reports statistics. The closure captures setup data (input arrays, parameters) so each iteration gets fresh-looking inputs without reconstructing them from scratch. `black_box(x)` is a fence that says "x is observable." The compiler treats it as if x was sent to some external system it can't inspect. Wrapping both inputs and the result ensures the computation isn't optimised away: `black_box(f(black_box(input)))`.

How It Works in Rust

use std::hint::black_box;
use std::time::Instant;

fn bench<T, F: FnMut() -> T>(name: &str, iters: usize, mut f: F) {
 // Warmup: prime instruction cache and branch predictor
 for _ in 0..iters / 10 {
     black_box(f());
 }

 let start = Instant::now();
 for _ in 0..iters {
     black_box(f());  // result is "observed" โ€” compiler keeps the computation
 }
 let per_iter = start.elapsed() / iters as u32;
 println!("{name}: {per_iter:?}/iter");
}

// Usage: closure captures the input data, called once per iteration.
let data: Vec<i64> = (0..1000).collect();
bench("sum", 100_000, || {
 black_box(data.iter().sum::<i64>())
});

// Parameterised bench: test the same algorithm at different input sizes.
fn bench_scaling<F: Fn(usize) -> i64>(name: &str, sizes: &[usize], f: F) {
 for &n in sizes {
     bench(&format!("{name} n={n}"), 10_000, || f(black_box(n)));
 }
}
For production benchmarking, use `criterion` (statistical analysis, HTML reports, regression detection) or `divan` (attribute macros, lower boilerplate). Both use the same `black_box` + warmup + many-iterations model internally.

What This Unlocks

Key Differences

ConceptOCamlRust
Benchmark closure`fun () -> computation``{ black_box(computation()) }`
Prevent optimisation`Sys.opaque_identity``std::hint::black_box`
Repeated executionManual `for` loopFramework calls `iter(...)`
Setup data in closureClosure captures`move` captures owned data
Parameterised benchMultiple separate functionsClosure factory per parameter
Production framework`Core_bench``criterion`, `divan`
//! # 530. Closures in Benchmarking
//! Patterns for measuring performance with closures and black_box.

use std::hint::black_box;
use std::time::{Duration, Instant};

/// Simple benchmark runner: time a closure over N iterations
fn bench<T, F: FnMut() -> T>(name: &str, iterations: usize, mut f: F) -> Duration {
    // Warmup
    for _ in 0..iterations / 10 {
        black_box(f());
    }

    let start = Instant::now();
    for _ in 0..iterations {
        // black_box prevents compiler from optimizing away the call
        black_box(f());
    }
    let elapsed = start.elapsed();
    let per_iter = elapsed / iterations as u32;
    println!("{}: {:?} total, {:?}/iter ({} iters)", name, elapsed, per_iter, iterations);
    elapsed
}

/// Parameterized benchmark: test same code with different input sizes
fn bench_scaling<T, F: Fn(usize) -> T>(name: &str, sizes: &[usize], f: F) {
    println!("\n{} โ€” scaling:", name);
    for &size in sizes {
        let iters = 1000.max(100_000 / size);
        bench(&format!("  n={}", size), iters, || f(black_box(size)));
    }
}

/// Compare two implementations
fn compare<T: std::fmt::Debug, A: Fn() -> T, B: Fn() -> T>(
    name_a: &str, a: A,
    name_b: &str, b: B,
    iterations: usize,
) {
    println!("\nComparing:");
    let t_a = bench(name_a, iterations, &a);
    let t_b = bench(name_b, iterations, &b);

    let ratio = t_a.as_nanos() as f64 / t_b.as_nanos().max(1) as f64;
    let winner = if ratio > 1.0 { name_b } else { name_a };
    println!("  {} is {:.2}x faster", winner, ratio.max(1.0 / ratio));
}

fn main() {
    println!("=== Basic timing ===");

    // Closure captures the workload โ€” black_box prevents dead-code elimination
    bench("sum_1_to_1000", 100_000, || {
        (1..=1000i64).sum::<i64>()
    });

    bench("string_concat_10", 10_000, || {
        (0..10).map(|i| i.to_string()).collect::<String>()
    });

    bench("vec_sort_100", 1_000, || {
        let mut v: Vec<i32> = (0..100).rev().collect();
        v.sort();
        v
    });

    // Scaling analysis
    bench_scaling("vec_sort", &[10, 100, 1000, 10000], |n| {
        let mut v: Vec<i32> = (0..n as i32).rev().collect();
        v.sort();
        v
    });

    // Compare implementations
    compare(
        "naive_sum",   || (0..1000i64).fold(0, |a, b| a + b),
        "iter_sum",    || (0..1000i64).sum::<i64>(),
        50_000,
    );

    // Closure factory for parameterized benchmarks
    let make_bench = |n: usize| move || {
        black_box((0..n).map(|x| x * x).sum::<usize>())
    };

    println!("\n=== Parameterized benches ===");
    for n in [100, 1000, 10000] {
        bench(&format!("sum_of_squares_n={}", n), 10_000, make_bench(n));
    }
}

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

    #[test]
    fn test_bench_runs() {
        // Verify bench function doesn't panic
        let elapsed = bench("test", 10, || 42i32);
        assert!(elapsed > Duration::ZERO);
    }

    #[test]
    fn test_black_box_identity() {
        // black_box returns its argument
        let x = black_box(42);
        assert_eq!(x, 42);
    }

    #[test]
    fn test_bench_closure_state() {
        // Closure can accumulate state โ€” each bench call resets
        let mut call_count = 0usize;
        bench("count", 100, || {
            call_count += 1;
            call_count
        });
        // 100 warmup/10 + 100 actual calls
        assert!(call_count > 0);
    }
}
(* Benchmarking with closures in OCaml *)
let time_it label f =
  let start = Unix.gettimeofday () in
  let result = f () in
  let elapsed = Unix.gettimeofday () -. start in
  Printf.printf "%s: %.6f seconds\n" label elapsed;
  result

(* Without Unix, simulate: *)
let benchmark label iterations f =
  let sum = ref 0 in
  for _ = 1 to iterations do
    sum := !sum + (f ())
  done;
  Printf.printf "%s: ran %d iterations (sum=%d to prevent opt)\n" label iterations !sum

let () =
  benchmark "sum_1000" 1000 (fun () ->
    let s = ref 0 in
    for i = 1 to 1000 do s := !s + i done;
    !s
  );
  benchmark "string_ops" 100 (fun () ->
    let s = String.concat "" (List.init 10 (fun i -> string_of_int i)) in
    String.length s
  )