rust-job-prep / phase-01 / day-01.rs
PHASE 01  |  DAY 01 / 15
// rust job ready series — phase 01: core rust

Ownership
& Borrowing

$ rustc day01.rs  →  zero_cost_memory_safety
Day 01 move copy clone references borrow checker lifetime

Ownership is Rust's core memory management model — every value has exactly one owner, and when the owner goes out of scope, the value is automatically dropped (freed). No garbage collector. No manual free. Enforced entirely at compile time.

Borrowing lets you use a value without taking ownership. You can have either any number of immutable references (&T) or exactly one mutable reference (&mut T) — never both at the same time. The borrow checker enforces this statically.

RULE 01

Single Owner

Each value in Rust has exactly one variable that owns it at any given time.

RULE 02

Move on Assignment

When ownership is assigned to another variable, the original variable is invalidated — this is a move. Types that implement Copy are the exception.

RULE 03

Drop on Scope Exit

When the owner goes out of scope, Rust automatically calls drop(), freeing heap memory. Zero runtime cost.

day01_move.rs MOVE
// String is heap-allocated → ownership MOVES, not copies
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;          // s1 is MOVED into s2
    // println!("{}", s1); // ❌ ERROR: value borrowed after move
    println!("{}", s2);   // ✅ s2 is the owner now
}
day01_copy.rs COPY
// Scalar types (i32, bool, f64, char) implement Copy — stack-only
fn main() {
    let x: i32 = 42;
    let y = x;            // COPY — x is still valid
    println!("{} {}", x, y); // ✅ both valid
}
day01_clone.rs CLONE
// .clone() deep-copies heap data — explicit, intentional cost
fn main() {
    let s1 = String::from("world");
    let s2 = s1.clone();  // deep copy of heap data
    println!("{} {}", s1, s2); // ✅ both valid, both own separate memory
}
day01_borrow.rs BORROW
// Immutable borrow — many readers, no mutation
fn print_len(s: &String) {   // borrows, does NOT take ownership
    println!("len = {}", s.len());
}

// Mutable borrow — exclusive writer
fn push_str(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let mut s = String::from("hello");
    print_len(&s);           // immutable borrow
    push_str(&mut s);       // mutable borrow
    println!("{}", s);      // ✅ "hello, world"
}
day01_conflict.rs COMPILE ERROR
fn main() {
    let mut s = String::from("hello");
    let r1 = &s;             // immutable borrow ✅
    let r2 = &s;             // another immutable borrow ✅
    let r3 = &mut s;        // ❌ cannot borrow as mutable — r1, r2 still active
    println!("{} {} {}", r1, r2, r3);
}
🚫

Zero Memory Bugs at Runtime

No dangling pointers, no use-after-free, no double-free. The borrow checker catches them all at compile time — your production binary ships without a class of bugs.

No GC Pauses

No garbage collector means predictable, low-latency performance. Critical for real-time systems, game engines, trading systems, and high-throughput web servers.

🔒

Thread Safety Guaranteed

Ownership rules extend to concurrency. You cannot accidentally share mutable state across threads — the compiler prevents data races entirely.

🔧

Explicit Resource Management

RAII via Drop: file handles, DB connections, mutex locks — all released deterministically when the owner goes out of scope. No leaks by design.

REAL-WORLD USAGE
AWS Firecracker — microVM runtime; ownership ensures no memory leaks across VM lifecycle
Cloudflare Workers — Rust's zero-GC borrow model enables microsecond-cold-start edge functions
Discord — switched from Go to Rust; eliminated GC latency spikes using ownership + drop semantics
Linux Kernel (Rust modules) — ownership prevents kernel memory safety bugs without performance cost
Q1 What is ownership in Rust, and why does Rust need it? +
Ownership is a compile-time memory management system where each value has a single owner. When the owner goes out of scope, the value is freed. Rust needs it to achieve memory safety without a garbage collector — giving you C-level performance with freedom from memory bugs like use-after-free and double-free. The compiler's borrow checker enforces ownership rules at zero runtime cost.
Q2 What is the difference between move, copy, and clone? +
Move: transfers ownership of heap-allocated data. The original variable is invalidated. Default for types like String, Vec.

Copy: implicitly duplicates stack-only data. The original stays valid. Types like i32, bool, f64 implement Copy.

Clone: explicit, deep copy of heap data via .clone(). Both variables own independent data. Use it consciously — it has a runtime cost.
Q3 How many mutable references can you have at a time, and why? +
Exactly one mutable reference (&mut T) at any point in time, and zero immutable references when a mutable reference is active. This prevents data races at compile time — if multiple references could mutate simultaneously, you'd have undefined behavior. Rust makes the concurrency contract explicit and statically verified.
Q4 What is a dangling reference, and how does Rust prevent it? +
A dangling reference points to memory that has already been freed. In C/C++, this causes undefined behavior. Rust's borrow checker tracks lifetimes and ensures a reference can never outlive the data it points to. If you try to return a reference to a local variable, the compiler rejects it at compile time — not at runtime, not in production.
Q5 When would you use &str vs String? +
Use &str (a string slice / borrowed view) for read-only access to string data — function parameters, string literals, slices of a String. It's zero-copy and cheap.

Use String when you need to own the string data, grow it, or return it from a function. The general rule: prefer &str in function signatures for flexibility; use String when you need ownership or mutation.
Q6 What does the Drop trait do, and how does it relate to ownership? +
The Drop trait defines custom cleanup logic that runs automatically when an owner goes out of scope — like a destructor. Rust calls drop() in reverse order of creation. This is RAII (Resource Acquisition Is Initialization): resources (file handles, locks, heap memory) are deterministically released when the owning struct is dropped. No GC, no explicit free needed.
Pattern What It Solves Used In
Rc<T> / Arc<T> Multiple ownership via reference counting. Arc is thread-safe. Use when you genuinely need shared ownership. Graph structures, shared caches, async runtimes
Cell<T> / RefCell<T> Interior mutability — mutate data through an immutable reference. RefCell moves borrow checking to runtime. UI trees, mock objects, single-threaded shared state
Cow<'a, B> Clone-on-Write — borrow data until mutation is needed, then clone. Avoids unnecessary allocations. Parsers, serializers, string processing in hot paths
Pin<T> Prevents a value from being moved in memory. Required for self-referential structs and async/await state machines. async futures, tokio tasks, custom allocators
ManuallyDrop<T> Opt out of automatic drop(). Gives you manual control over destruction order. Used in unsafe code. FFI, custom allocators, arena memory management
day01_advanced.rs — Rc + RefCell interior mutability ADVANCED
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    // Multiple owners + interior mutability without unsafe
    let shared = Rc::new(RefCell::new(vec![1, 2, 3]));

    let clone_a = Rc::clone(&shared);
    let clone_b = Rc::clone(&shared);

    clone_a.borrow_mut().push(4);   // runtime borrow check
    clone_b.borrow_mut().push(5);

    println!("{:?}", shared.borrow()); // [1, 2, 3, 4, 5]
    // ⚠️  panics at runtime if you violate borrow rules
    // Use Arc> instead for multi-threaded scenarios
}