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

101: Move Semantics

Difficulty: 2 Level: Intermediate When you pass a value to a function in Rust, ownership transfers โ€” the original is gone.

The Problem This Solves

In C, you can pass a pointer to a function, free the memory inside, and then use the original pointer again. This is a use-after-free bug โ€” one of the most dangerous vulnerabilities in systems programming. The compiler says nothing. Your program corrupts memory silently, or crashes unpredictably, or opens a security hole. Python and most other languages sidestep this with garbage collection: every value is reference-counted or traced, so the memory is never freed while someone still holds a pointer to it. But GC has a cost: pauses, overhead, and the runtime can't know at compile time whether you'll use a value again. Rust takes a third path. It tracks ownership statically โ€” at compile time. Every value has exactly one owner. When that owner goes out of scope, the value is dropped. When you pass a value to a function, ownership transfers: the original binding becomes invalid. The compiler proves โ€” without a GC, without runtime checks โ€” that use-after-free is impossible.

The Intuition

Every value has exactly one owner; passing a value to a function gives away that ownership, making the original variable invalid โ€” like handing someone your only key. This isn't arbitrary. If there's only ever one owner, the compiler knows exactly when to free memory: when the owner's scope ends. No GC needed. No runtime. No surprises.

How It Works in Rust

// ERROR: value used after move
fn broken() {
 let name = String::from("Alice");
 let len = use_string(name); // ownership moves here
 println!("{}", name);       // ERROR: borrow of moved value: `name`
}

// FIX 1: clone before passing (explicit copy)
fn with_clone() {
 let name = String::from("Alice");
 let len = use_string(name.clone()); // clone stays, original moves
 let len2 = use_string(name);        // now name moves here โ€” fine
}

// FIX 2: pass a reference instead (borrow, don't move)
fn use_string_ref(s: &String) -> usize {
 s.len() // s is borrowed, not owned โ€” caller keeps it
}

fn with_borrow() {
 let name = String::from("Alice");
 let len = use_string_ref(&name); // borrow: name is still valid
 println!("{} has {} chars", name, len); // works!
}

// Copy types don't move โ€” they're silently duplicated
fn copy_demo() {
 let x: i32 = 42;
 let y = x;        // Copy, not move โ€” both remain valid
 println!("{} {}", x, y); // fine
}
The rule: heap-allocated types (`String`, `Vec`, structs with those fields) move. Stack-only types (`i32`, `bool`, `f64`, `char`) copy silently.

What This Unlocks

Key Differences

ConceptOCamlRust
Memory managementGC (reference counting + tracing)Ownership (compile-time)
Passing a valueShares a GC pointer โ€” original stays validMoves ownership โ€” original invalid
Reuse after passAlways worksOnly if `Copy` or explicitly `.clone()`d
"Use after move"Impossible conceptCompile error
Runtime costGC pauses and overheadZero-cost (no GC)
// Example 101: Move Semantics โ€” Rust Ownership Transfer
//
// In Rust, values have a single owner. When you pass a value to a function,
// ownership transfers (moves) and the original binding becomes invalid.

// Approach 1: Demonstrating move with String (heap-allocated)
fn use_string(s: String) -> usize {
    println!("Using: {}", s);
    s.len()
    // s is dropped here
}

fn approach1() {
    let greeting = String::from("Hello, ownership!");
    let len1 = use_string(greeting);
    // greeting is MOVED โ€” can't use it again!
    // let len2 = use_string(greeting); // ERROR: value used after move
    
    // Instead, we must create a new one or clone
    let greeting2 = String::from("Hello, ownership!");
    let len2 = use_string(greeting2);
    assert_eq!(len1, len2);
    println!("Lengths: {}, {}", len1, len2);
}

// Approach 2: Using clone to keep both copies
fn approach2() {
    let greeting = String::from("Hello, ownership!");
    let len1 = use_string(greeting.clone()); // clone before move
    let len2 = use_string(greeting);         // now move the original
    assert_eq!(len1, len2);
    println!("Clone approach โ€” both used successfully");
}

// Approach 3: Move with structs
#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn greet(p: Person) {
    println!("Hello, {} (age {})!", p.name, p.age);
    // p is consumed here
}

fn approach3() {
    let p = Person {
        name: String::from("Alice"),
        age: 30,
    };
    greet(p);
    // greet(p); // ERROR: p was moved
    
    // With Copy types (like integers), no move occurs
    let x = 42;
    let y = x; // Copy, not move
    println!("x={}, y={} โ€” both valid because i32 is Copy", x, y);
}

fn main() {
    println!("=== Approach 1: Basic Move ===");
    approach1();
    println!("\n=== Approach 2: Clone Before Move ===");
    approach2();
    println!("\n=== Approach 3: Struct Move vs Copy Types ===");
    approach3();
}

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

    #[test]
    fn test_string_move() {
        let s = String::from("test");
        let len = use_string(s);
        assert_eq!(len, 4);
    }

    #[test]
    fn test_clone_preserves_value() {
        let s = String::from("hello");
        let s2 = s.clone();
        assert_eq!(s, s2);
        assert_eq!(use_string(s), use_string(s2));
    }

    #[test]
    fn test_copy_types_dont_move() {
        let x: i32 = 42;
        let y = x;
        assert_eq!(x, y); // both still valid
    }

    #[test]
    fn test_move_into_vec() {
        let s = String::from("hello");
        let mut v = Vec::new();
        v.push(s);
        // s is moved into vec
        assert_eq!(v[0], "hello");
    }

    #[test]
    fn test_move_out_of_option() {
        let mut opt = Some(String::from("data"));
        let taken = opt.take(); // moves out, replaces with None
        assert!(opt.is_none());
        assert_eq!(taken.unwrap(), "data");
    }
}
(* Example 101: Move Semantics โ€” OCaml GC vs Rust Ownership Transfer *)

(* In OCaml, all values are garbage-collected. "Moving" doesn't exist โ€”
   you just share references and the GC cleans up. *)

(* Approach 1: Simple value passing โ€” OCaml shares freely *)
let use_string s =
  Printf.printf "Using: %s\n" s;
  String.length s

let approach1 () =
  let greeting = "Hello, ownership!" in
  let len1 = use_string greeting in
  (* In OCaml, we can use greeting again โ€” no move happened *)
  let len2 = use_string greeting in
  assert (len1 = len2);
  Printf.printf "Used greeting twice, lengths: %d, %d\n" len1 len2

(* Approach 2: Passing structured data โ€” still no move *)
type person = { name : string; age : int }

let greet p =
  Printf.printf "Hello, %s (age %d)!\n" p.name p.age

let approach2 () =
  let p = { name = "Alice"; age = 30 } in
  greet p;
  greet p;  (* No problem โ€” p is still accessible *)
  Printf.printf "Person %s is still here\n" p.name

(* Approach 3: Simulating ownership transfer with option ref *)
let take_ownership (slot : string option ref) =
  match !slot with
  | Some s ->
    slot := None;  (* "consume" the value *)
    Printf.printf "Took ownership of: %s\n" s;
    s
  | None ->
    failwith "Value already moved!"

let approach3 () =
  let data = ref (Some "precious data") in
  let _taken = take_ownership data in
  (* Trying again would fail โ€” simulating Rust's move *)
  (try
     let _ = take_ownership data in ()
   with Failure msg ->
     Printf.printf "Caught: %s\n" msg);
  Printf.printf "Simulated move semantics in OCaml\n"

(* Tests *)
let () =
  approach1 ();
  approach2 ();
  approach3 ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Move Semantics

Value Passing

OCaml โ€” values are shared via GC:

๐Ÿช Show OCaml equivalent
let use_string s =
Printf.printf "Using: %s\n" s;
String.length s

let () =
let greeting = "Hello!" in
let _ = use_string greeting in
let _ = use_string greeting in  (* fine โ€” GC manages it *)
()

Rust โ€” values are moved (consumed):

fn use_string(s: String) -> usize {
 println!("Using: {}", s);
 s.len()
}

fn main() {
 let greeting = String::from("Hello!");
 let _ = use_string(greeting);
 // let _ = use_string(greeting); // ERROR: moved
}

Keeping Both Copies

OCaml โ€” automatic, no action needed:

๐Ÿช Show OCaml equivalent
let a = [1; 2; 3] in
let b = a in  (* both a and b point to same list *)
assert (a = b)

Rust โ€” explicit clone required:

let a = vec![1, 2, 3];
let b = a.clone();  // explicit copy
// let b = a;        // this would MOVE, making a invalid
assert_eq!(a, b);

Simulating Move in OCaml

OCaml โ€” manual protocol with option ref:

๐Ÿช Show OCaml equivalent
let take (slot : string option ref) =
match !slot with
| Some s -> slot := None; s
| None -> failwith "already moved"

Rust โ€” built into the language:

let mut opt = Some(String::from("data"));
let taken = opt.take(); // Option::take moves out
assert!(opt.is_none());

Copy Types

OCaml โ€” everything is "copy-like" (GC-managed):

๐Ÿช Show OCaml equivalent
let x = 42 in
let y = x in  (* both valid, always *)

Rust โ€” only `Copy` types avoid move:

let x: i32 = 42;
let y = x;  // Copy, not move โ€” both valid
let s = String::from("hi");
let t = s;  // Move! s is invalid now