//! # 506. Move Closures and Ownership
//! The `move` keyword transfers ownership into closures for safe sharing.
use std::thread;
/// Without move: closure borrows x โ can't outlive x's scope
fn borrow_closure() {
let x = 10;
let f = |n| n + x; // borrows x by &i32
println!("borrowed: {}", f(5));
println!("x still usable: {}", x);
}
/// With move: closure owns x โ can be sent to another thread
fn move_closure_thread() {
let data = vec![1, 2, 3, 4, 5];
// Without `move`, error: data borrowed but might outlive thread
let handle = thread::spawn(move || {
let sum: i32 = data.iter().sum();
println!("Thread sum: {}", sum);
sum
});
// data is MOVED โ can't use it here
let result = handle.join().unwrap();
println!("Thread returned: {}", result);
}
/// Move closure for returning from function (outlives local scope)
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
// n must be moved because it lives on the stack of make_adder
move |x| x + n
}
/// Multiple move closures each get their own copy
fn multiple_move_closures() {
let value = 42i32;
let add = move |x: i32| x + value; // value copied into add
let mul = move |x: i32| x * value; // value copied into mul
println!("add(8) = {}, mul(2) = {}", add(8), mul(2));
println!("value still usable: {}", value); // Copy type โ still here
}
/// Moving a non-Copy type (String)
fn move_string() {
let msg = String::from("hello from closure");
let print_msg = move || println!("{}", msg);
// msg is MOVED into print_msg โ can't use msg here
print_msg();
print_msg(); // can still call the closure multiple times
}
/// Move closure as channel producer
fn channel_example() {
let (tx, rx) = std::sync::mpsc::channel();
let data = vec!["a", "b", "c"];
let handle = thread::spawn(move || {
for item in data { // data moved into closure, then consumed
tx.send(item).unwrap();
}
});
while let Ok(msg) = rx.recv() {
println!("received: {}", msg);
}
handle.join().unwrap();
}
fn main() {
println!("=== Borrow closure ===");
borrow_closure();
println!("\n=== Move to thread ===");
move_closure_thread();
println!("\n=== Return from function (requires move) ===");
let add5 = make_adder(5);
println!("add5(10) = {}", add5(10));
println!("\n=== Multiple move closures ===");
multiple_move_closures();
println!("\n=== Move String ===");
move_string();
println!("\n=== Channel with move closure ===");
channel_example();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_make_adder() {
let f = make_adder(100);
assert_eq!(f(1), 101);
assert_eq!(f(0), 100);
}
#[test]
fn test_move_in_thread() {
let v = vec![1, 2, 3];
let h = thread::spawn(move || v.iter().sum::<i32>());
assert_eq!(h.join().unwrap(), 6);
}
#[test]
fn test_move_copy_type() {
let n = 5i32;
let f = move || n * 2;
let g = move || n + 10;
assert_eq!(f(), 10);
assert_eq!(g(), 15);
assert_eq!(n, 5); // n is Copy, still accessible
}
}
(* OCaml: closures always capture by reference to GC-managed heap *)
(* No explicit move needed โ GC handles lifetime *)
let make_counter_fn start =
let n = ref start in
fun () -> incr n; !n
let spawn_counter name start =
let counter = make_counter_fn start in
(* OCaml Domain for parallelism (4.5+) *)
let _ = name in (* suppress unused warning *)
(* Simulate with a closure that "owns" its state *)
counter
let () =
(* Closure captures 'data' โ still usable after creating closure *)
let data = [1; 2; 3; 4; 5] in
let sum_closure = fun () -> List.fold_left (+) 0 data in
Printf.printf "sum = %d\n" (sum_closure ());
Printf.printf "data still accessible: %d\n" (List.length data);
(* Counter with "owned" state via ref *)
let c1 = spawn_counter "counter1" 0 in
let c2 = spawn_counter "counter2" 100 in
Printf.printf "c1: %d %d %d\n" (c1()) (c1()) (c1());
Printf.printf "c2: %d %d %d\n" (c2()) (c2()) (c2());
(* Multiple closures over same data *)
let value = 42 in
let add = fun x -> x + value in
let mul = fun x -> x * value in
Printf.printf "add(8) = %d, mul(2) = %d\n" (add 8) (mul 2)