// Example 121: Closure Capture Modes
//
// Rust closures capture variables in three ways:
// 1. By shared reference (&T) โ default for read-only
// 2. By mutable reference (&mut T) โ when mutating
// 3. By move (T) โ with `move` keyword or when consuming
// Approach 1: Capture by shared reference
fn approach1() {
let x = 42;
let f = || x + 1; // captures &x
assert_eq!(f(), 43);
assert_eq!(x, 42); // x still accessible
println!("f() = {}, x = {}", f(), x);
}
// Approach 2: Capture by mutable reference
fn approach2() {
let mut total = 0;
{
let mut add = |x: i32| total += x; // captures &mut total
add(10);
add(20);
add(30);
} // mutable borrow ends here
assert_eq!(total, 60);
println!("Total: {}", total);
}
// Approach 3: Capture by move
fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor // `move` takes ownership of factor
// Without `move`, factor would be borrowed โ but it would
// be dropped when make_multiplier returns!
}
fn approach3() {
let double = make_multiplier(2);
let triple = make_multiplier(3);
assert_eq!(double(5), 10);
assert_eq!(triple(5), 15);
println!("double(5)={}, triple(5)={}", double(5), triple(5));
// Move with non-Copy types
let name = String::from("Alice");
let greet = move || format!("Hello, {}!", name);
// name is moved โ can't use it here
println!("{}", greet());
}
fn main() {
println!("=== Approach 1: By Shared Reference ===");
approach1();
println!("\n=== Approach 2: By Mutable Reference ===");
approach2();
println!("\n=== Approach 3: By Move ===");
approach3();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_capture_by_ref() {
let v = vec![1, 2, 3];
let len = || v.len(); // borrows v
assert_eq!(len(), 3);
assert_eq!(v, vec![1, 2, 3]); // v still accessible
}
#[test]
fn test_capture_by_mut_ref() {
let mut count = 0;
let mut inc = || count += 1;
inc();
inc();
drop(inc);
assert_eq!(count, 2);
}
#[test]
fn test_capture_by_move() {
let s = String::from("hello");
let f = move || s.len();
assert_eq!(f(), 5);
// s is moved โ would fail: assert_eq!(s, "hello");
}
#[test]
fn test_move_copy_type() {
let x = 42;
let f = move || x; // moves, but i32 is Copy so x is still valid
assert_eq!(f(), 42);
assert_eq!(x, 42); // still works! i32 is Copy
}
#[test]
fn test_returned_closure() {
let f = make_multiplier(10);
assert_eq!(f(3), 30);
assert_eq!(f(7), 70);
}
}
(* Example 121: Closure Capture Modes *)
(* OCaml closures always capture by reference (GC-managed).
Rust closures capture by reference, mutable reference, or move. *)
(* Approach 1: Capture by reference (immutable) *)
let approach1 () =
let x = 42 in
let f = fun () -> x + 1 in (* captures x by reference *)
assert (f () = 43);
assert (x = 42); (* x unchanged *)
Printf.printf "f() = %d, x = %d\n" (f ()) x
(* Approach 2: Capture mutable state *)
let approach2 () =
let total = ref 0 in
let add x = total := !total + x in
add 10; add 20; add 30;
assert (!total = 60);
Printf.printf "Total: %d\n" !total
(* Approach 3: Closure outliving local scope *)
let make_multiplier factor =
fun x -> x * factor (* factor captured, GC keeps it alive *)
let approach3 () =
let double = make_multiplier 2 in
let triple = make_multiplier 3 in
assert (double 5 = 10);
assert (triple 5 = 15);
Printf.printf "double(5)=%d, triple(5)=%d\n" (double 5) (triple 5)
let () =
approach1 ();
approach2 ();
approach3 ();
Printf.printf "โ All tests passed\n"