Rust Ownership

The Three Rules

Every value has exactly one owner
let s1 = String::from("hello");
let s2 = s1;    // s1 is MOVED to s2
// println!("{}", s1);  // ERROR: s1 is no longer valid
println!("{}", s2);     // "hello"

Assignment of heap data moves ownership. The original variable is invalidated. This prevents double-free at compile time.

Move vs Copy

Stack types implement Copy — heap types move
// Copy types: i32, f64, bool, char, tuples of Copy types
let x = 42;
let y = x;      // x is COPIED (bits duplicated)
println!("{x}"); // works — x is still valid

// Heap types: String, Vec<T>, Box<T>
let s1 = String::from("hello");
let s2 = s1;    // s1 is MOVED
// s1 is gone

// Explicit deep copy
let s3 = s2.clone();
println!("{s2}"); // "hello" — s2 still valid
println!("{s3}"); // "hello" — independent copy

Copy types live entirely on the stack. The compiler copies them bitwise on assignment. Heap types transfer ownership on assignment — clone() makes an independent deep copy.

Ownership Transfer in Functions

Functions take or return ownership
fn take_ownership(s: String) {
    println!("took: {s}");
}  // s is dropped here

fn give_ownership() -> String {
    String::from("hello")  // ownership moves to caller
}

fn main() {
    let s = String::from("hello");
    take_ownership(s);
    // s is no longer valid here

    let s2 = give_ownership();
    println!("{s2}");  // "hello"
}

Passing a heap value to a function moves it. The caller loses access. Return values transfer ownership back. To use a value after passing it, either clone it or use references (borrowing).

Scope and Drop

Values are dropped when their owner goes out of scope
{
    let s = String::from("hello");
    // s is valid here
}  // s goes out of scope — drop is called, memory freed

// Custom drop
struct Connection {
    host: String,
}

impl Drop for Connection {
    fn drop(&mut self) {
        println!("closing connection to {}", self.host);
    }
}

Drop is Rust’s destructor. It runs automatically when a value goes out of scope. You cannot call drop() directly on a value — use std::mem::drop(value) to drop early.

Smart Pointers

Box, Rc, Arc — different ownership models
// Box<T> — heap allocation, single owner
let b = Box::new(42);
println!("{}", *b);  // dereference

// Rc<T> — reference counting, single thread
use std::rc::Rc;
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a);  // increment count, not deep copy
println!("count: {}", Rc::strong_count(&a));  // 2

// Arc<T> — atomic reference counting, thread-safe
use std::sync::Arc;
let data = Arc::new(vec![1, 2, 3]);
let data_clone = Arc::clone(&data);
std::thread::spawn(move || {
    println!("{:?}", data_clone);
});

Box is single ownership on the heap. Rc shares ownership within a single thread. Arc shares across threads. Rc::clone is cheap — it increments a counter, not a deep copy.