Rust's Memory Model: What Systems Programmers Actually Need to Know
Most Rust tutorials stop at ownership and borrowing. But the real power — and the real complexity — lies in understanding the memory model that makes safe concurrency possible without a garbage collector.
The Ownership Stack
Rust's three rules are deceptively simple:
- Each value has exactly one owner
- When the owner goes out of scope, the value is dropped
- You can have either one mutable reference OR any number of immutable references
But these rules are enforced at compile time through a sophisticated type system that tracks lifetimes:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
The lifetime parameter 'a tells the compiler: the returned reference lives at least as long as the shorter of the two input references.
Interior Mutability
Sometimes you need to mutate data behind a shared reference. Rust provides escape hatches that move the borrow check to runtime:
use std::cell::RefCell;
use std::rc::Rc;
// Multiple owners, runtime borrow checking
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
let clone = Rc::clone(&shared);
shared.borrow_mut().push(4);
println!("{:?}", clone.borrow()); // [1, 2, 3, 4]
RefCell panics on double mutable borrow at runtime. In concurrent code, use Mutex or RwLock instead — they block rather than panic.
The Unsafe Boundary
unsafe doesn't turn off the borrow checker. It allows exactly five additional operations:
- Dereferencing raw pointers
- Calling unsafe functions
- Accessing mutable statics
- Implementing unsafe traits
- Accessing fields of unions
The key insight: unsafe is a contract, not an escape. You're telling the compiler "I've verified the invariants you can't check."
Zero-Cost Abstractions in Practice
Consider Vec<T>. It's a safe abstraction over a raw heap allocation:
pub struct Vec<T> {
ptr: *mut T, // Raw pointer (unsafe to deref)
len: usize,
cap: usize,
}
Every method on Vec maintains the invariant that ptr points to a valid allocation of cap elements, with len elements initialized. The safe API makes it impossible to violate these invariants. The unsafe implementation makes it possible to build.
This is the pattern that makes Rust unique: unsafe implementations, safe interfaces, zero runtime cost.