๐Ÿฆ€ Functional Rust
๐ŸŽฌ Rust Ownership in 30 seconds Visual walkthrough of ownership, moves, and automatic memory management.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Each value in Rust has exactly one owner โ€” when the owner goes out of scope, the value is dropped

โ€ข Assignment moves ownership by default; the original binding becomes invalid

โ€ข Borrowing (&T / &mut T) lets you reference data without taking ownership

โ€ข The compiler enforces: many shared references OR one mutable reference, never both

โ€ข No garbage collector needed โ€” memory is freed deterministically at scope exit

506: Move Closures and Ownership

Difficulty: 3 Level: Intermediate The `move` keyword gives closures full ownership of their captures โ€” essential for threads, async tasks, and returning closures from functions.

The Problem This Solves

You write `thread::spawn(|| { use_data(data) })` and get: `closure may outlive the current function, but it borrows data`. The thread might outlive the function that created it โ€” Rust refuses to let a thread borrow something that might be deallocated while the thread is still running. The same error appears when returning closures from functions: `cannot return value referencing local variable`. The closure borrows something on the stack, but the stack is gone after the function returns. Without `move`, closures borrow their environment, creating lifetime constraints that the borrow checker enforces strictly. These errors protect you from use-after-free bugs โ€” the kind that cause CVEs in C/C++.

The Intuition

A borrowing closure is like a library checkout form โ€” it says "I need this book for a while, please don't throw it away." When the function returns or the thread outlives the scope, the form expires but the book might already be gone. A `move` closure is like buying the book โ€” you own it now. No return date, no dependency on the original shelf. The closure carries its own copy of everything it needs. In Python and JavaScript, all closures implicitly own their captured values (or the GC keeps them alive). Rust makes the distinction explicit: borrow (default, free, has lifetime constraints) vs. own (`move`, may copy/move data, no lifetime constraints).

How It Works in Rust

// Without move: closure borrows โ€” constrained to outlive 'x
let x = 10;
let f = |n| n + x;     // borrows x as &i32 โ€” fine here
println!("{}", f(5));  // x still usable

// With move: required for threads
let data = vec![1, 2, 3, 4, 5];
let handle = thread::spawn(move || {       // data MOVED into closure
 let sum: i32 = data.iter().sum();
 println!("Thread sum: {}", sum);
 sum
});
// data is gone from this scope โ€” the thread owns it now
let result = handle.join().unwrap();

// With move: required for returning from functions
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
 move |x| x + n    // n moved into closure; closure owns it
}                      // make_adder's stack is gone, but closure survives

// Copy types: both owner and closure have the value
let value = 42i32;    // i32 is Copy
let f1 = move || value * 2;   // value COPIED into f1
let f2 = move || value + 10;  // value COPIED into f2
println!("{}", value); // original still usable โ€” it was copied, not moved

// Non-Copy types: ownership transfers
let msg = String::from("hello");
let print_msg = move || println!("{}", msg);   // String MOVED
// println!("{}", msg);  // โœ— ERROR โ€” msg was moved
print_msg(); // still callable โ€” closure owns the String
print_msg(); // Fn: can call multiple times

What This Unlocks

Key Differences

ConceptOCamlRust
Closure lifetimeGC handles โ€” closures live foreverBounded by captures unless `move`
Thread closureGC-safe by default`move` required for `Send + 'static`
Ownership transferImplicit (GC traces references)Explicit `move` keyword
Non-Copy type in closureShared via GCMoved in โ€” original binding invalidated
Copy type in closureAlways sharedCopied โ€” both owner and closure have it
//! # 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)