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

540: Borrow Checker Internals

Difficulty: 4 Level: Advanced The borrow checker enforces three rules that together prevent data races, use-after-free, and dangling pointers. Understanding why the rules exist makes the errors navigable rather than mysterious.

The Problem This Solves

Rust's borrow checker is sometimes seen as an obstacle. But every rule has a concrete unsafe behavior it prevents. The three core rules: 1. Any number of `&T` at once, OR exactly one `&mut T` โ€” never both at the same time. 2. References must always be valid (no dangling references). 3. Moved values cannot be used again. Without rule 1, two threads could race on the same data. Without rule 2, you'd have use-after-free. Without rule 3, you'd use freed memory after a move destructs it. The borrow checker is a static proof that none of these happen in your program.

The Intuition

Think of borrowing like a bank vault: The borrow checker verifies these rules at compile time, using lifetime analysis to determine when borrows begin and end. With NLL, borrows end at their last use โ€” not at the closing brace.

How It Works in Rust

Rule 1 โ€” shared borrows are compatible, exclusive borrows aren't:
let v = vec![1, 2, 3];
let r1 = &v;           // shared borrow โ€” OK
let r2 = &v;           // another shared borrow โ€” still OK
println!("{:?} {:?}", r1, r2);  // both used โ€” borrows end here (NLL)

// After NLL ends r1 and r2:
let mut v = vec![1, 2, 3];
v.push(4);             // mutable borrow โ€” fine, no shared borrows active
Rule 1 violated โ€” error with diagnostic:
let mut v = vec![1, 2, 3];
let r = &v;       // shared borrow โ€” active
v.push(4);        // ERROR: cannot borrow `v` as mutable because it's also borrowed as immutable
println!("{}", r[0]); // r still live here โ€” the overlap is real
Rule 2 โ€” no dangling references:
// The compiler prevents this:
fn dangle() -> &str {
 let s = String::from("hello");
 &s // ERROR: s is dropped at end of function โ€” reference would dangle
}
// Fix: return String (owned), not &str (borrow of local)
Patterns to work within the rules:
let mut v = vec![1, 2, 3, 4, 5];

// Pattern 1: Copy types don't borrow
let first = v[0]; // i32 is Copy โ€” no borrow created
v.push(first * 10); // fine

// Pattern 2: Split borrows โ€” borrow checker tracks field granularity
let (left, right) = v.split_at_mut(3);
left[0] = 999;   // mutating left half
right[0] = 888;  // mutating right half โ€” no overlap!

// Pattern 3: End the borrow before mutating
{
 let r = &v[0]; // borrow starts
 println!("{}", r); // borrow ends here (NLL โ€” last use)
} // or just let the block end
v.push(100); // fine
The "borrow graph" the compiler builds: The compiler tracks which variables are borrowed, from when to when, and whether shared or exclusive. If any two borrows overlap and one is exclusive, it's rejected. This analysis happens over the control flow graph โ€” including branches, loops, and early returns.

What This Unlocks

Key Differences

ConceptOCamlRust
Shared mutable stateAllowed โ€” discipline required, GC manages memoryStatically forbidden: shared XOR mutable, enforced by borrow checker
Data racesRuntime error (or silent corruption)Compile-time error โ€” rules prevent aliased mutation
Use after freeImpossible โ€” GC keeps it aliveImpossible โ€” borrow checker ensures references don't outlive data
Moved valuesGC never "moves" โ€” always accessibleOnce moved, original binding is invalid โ€” compiler enforces this
VerificationTesting, code review, runtime checksStatic โ€” compile-time proof that aliasing rules hold for all paths
//! # 540. Borrow Checker Internals
//! Understanding why the borrow checker's rules exist and how to work with them.

/// Rule 1: Cannot have &mut while & exists
fn rule_one_demo() {
    let mut v = vec![1, 2, 3];

    let r1 = &v;     // shared borrow
    let r2 = &v;     // another shared borrow โ€” OK!
    println!("r1: {:?}, r2: {:?}", r1, r2);
    // r1 and r2 last used above โ€” borrows end here (NLL)

    v.push(4);       // mutable borrow โ€” OK now (r1, r2 no longer live)
    println!("after push: {:?}", v);
}

/// Rule 2: Cannot have two &mut at the same time
fn rule_two_demo() {
    let mut x = 5;
    let r1 = &mut x;  // exclusive mutable borrow
    *r1 += 1;         // use r1
    drop(r1);         // r1 ends here
    let r2 = &mut x;  // new mutable borrow โ€” OK (r1 ended)
    *r2 *= 2;
    drop(r2);
    println!("x = {}", x); // 12: (5+1)*2
}

/// Rule 3: Cannot use moved value
fn rule_three_demo() {
    let s1 = String::from("hello");
    let s2 = s1;          // s1 MOVED to s2
    // println!("{}", s1); // ERROR: s1 was moved
    println!("s2: {}", s2);

    // Clone to keep both:
    let s3 = String::from("world");
    let s4 = s3.clone();  // s3 still valid
    println!("{} and {}", s3, s4);
}

/// Understanding the "borrow graph"
fn borrow_graph_demo() {
    let mut data = vec![1, 2, 3, 4, 5];

    // Reads are non-exclusive: any number allowed
    let sum: i32 = data.iter().sum();
    let count = data.len();
    println!("sum={}, count={} (multiple shared borrows)", sum, count);
    // Borrows ended above

    // Write is exclusive: only one at a time
    data[0] *= 10;
    data.retain(|&x| x > 5);
    println!("filtered: {:?}", data);
}

/// Patterns to work around borrow checker
fn workaround_patterns() {
    let mut v = vec![1, 2, 3, 4, 5];

    // Pattern 1: Clone to read while also mutating
    let first = v[0]; // Copy type โ€” no borrow needed
    v.push(first * 10);
    println!("v: {:?}", v);

    // Pattern 2: Use index instead of reference
    let len = v.len();
    for i in 0..len {
        v[i] *= 2; // indexed access avoids conflicting borrows
    }
    println!("doubled: {:?}", v);

    // Pattern 3: Split the borrow
    let (left, right) = v.split_at_mut(3);
    left[0] = 999;
    right[0] = 888;
    println!("split-mutated: {:?}", v);
}

fn main() {
    println!("=== Rule 1: Shared borrows are compatible ===");
    rule_one_demo();

    println!("\n=== Rule 2: Exclusive &mut โ€” only one at a time ===");
    rule_two_demo();

    println!("\n=== Rule 3: Move semantics ===");
    rule_three_demo();

    println!("\n=== Borrow graph analysis ===");
    borrow_graph_demo();

    println!("\n=== Workaround patterns ===");
    workaround_patterns();
}

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

    #[test]
    fn test_shared_borrows() {
        let v = vec![1, 2, 3];
        let a = &v;
        let b = &v;
        assert_eq!(a.len(), b.len());
    }

    #[test]
    fn test_sequential_mut_borrows() {
        let mut x = 1;
        { let r = &mut x; *r = 2; }
        { let r = &mut x; *r = 3; }
        assert_eq!(x, 3);
    }

    #[test]
    fn test_split_borrow() {
        let mut v = [1, 2, 3, 4];
        let (l, r) = v.split_at_mut(2);
        l[0] = 10;
        r[0] = 30;
        assert_eq!(v, [10, 2, 30, 4]);
    }
}
(* Borrow checker concepts in OCaml โ€” enforced by discipline, not type system *)

(* OCaml: you CAN have multiple mutable aliases โ€” programmer must be careful *)
let () =
  let x = ref 42 in
  let alias1 = x in  (* both point to same ref *)
  let alias2 = x in

  (* This is "data race" prone in concurrent code โ€” OCaml allows it *)
  alias1 := 100;
  alias2 := 200;
  Printf.printf "x = %d (aliased mutation โ€” potentially unsafe)\n" !x;

  (* Safe pattern: use x, then modify โ€” explicit discipline *)
  let data = ref [| 1; 2; 3 |] in
  let snapshot = Array.copy !data in  (* defensive copy, not a borrow *)
  !data.(0) <- 99;
  Printf.printf "snapshot[0] = %d, data[0] = %d\n" snapshot.(0) !data.(0)