Rust's reputation for a steep learning curve traces almost entirely to one idea: ownership. Most languages manage memory with garbage collectors (tracing what's reachable at runtime) or manual allocation (programmer's problem entirely). Rust refuses both, instead enforcing memory rules at compile time through ownership — which is why Rust programs don't segfault or leak in the ways C does, yet run with no runtime cost. The compiler feels strict until you internalize its mental model; then it reads like a very careful reviewer.
Rule one: every value has exactly one owner#
let s1 = String::from("hello");
let s2 = s1; // ownership MOVES to s2
// println!("{}", s1); // compile error: s1 no longer valid
In most languages, s2 = s1 copies a reference and both variables stay valid — setting up use-after-free and double-free bugs that only explode at runtime. Rust's move semantics make the danger unrepresentable: when ownership transfers, the old name simply stops existing. When the owner goes out of scope, the value is dropped automatically. One owner, deterministic cleanup, zero garbage collector.
Primitives (int, bool, f64) are the exception — they're cheap Copy types that duplicate on assignment. The move rule applies to anything heap-allocated: strings, vectors, structs containing them.
Rule two: borrowing lets you use without owning#
Moving ownership away just to read a value would be absurd, so functions take references:
fn length(s: &String) -> usize { // borrows, doesn't own
s.len()
}
let s = String::from("hello");
length(&s); // lend it out
println!("{}", s); // still ours, still valid
Borrowing splits into two regimes, and their coexistence rule is the famous constraint:
- Any number of immutable borrows (
&T) — many readers, safe simultaneously - Exactly one mutable borrow (
&mut T) — one writer, exclusive
Readers and writers never overlap; two writers never overlap. This kills data races at compile time — the class of bug that plagues every other systems language. When the compiler rejects code like:
let mut v = vec![1, 2, 3];
let first = &v[0]; // immutable borrow starts
v.push(4); // error! mutating while borrowed
println!("{first}"); // ...because this could read freed memory
…it isn't pedantry. Pushing may reallocate the vector's buffer; first would dangle. Every "annoying" borrow-checker error you'll ever meet reduces to some version of this real hazard. The fix patterns become reflexes: restructure so borrows don't overlap, clone explicitly when cheap, or scope the borrow shorter:
let first = { let f = &v[0]; *f }; // borrow ends here
v.push(4); // now fine
Lifetimes: names for how long references live#
References must never outlive their data. Most cases are locally obvious and the compiler infers them silently. Lifetimes ('a) exist for the ambiguous ones — chiefly functions returning references derived from inputs:
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
Read 'a not as a type but as a constraint: "the returned reference lives no longer than both inputs." The annotation doesn't change behavior; it documents the contract so the compiler can enforce it at every call site. Interview-relevant summary: lifetimes are the bookkeeping that makes borrowing sound across function boundaries — mostly invisible, occasionally explicit, always checked statically.
The vocabulary interviews actually probe#
- Move vs copy — heap values transfer ownership; stack primitives duplicate
&Tvs&mut T— shared vs exclusive access, enforced to never coexist- Why no data races? — aliasing XOR mutation, checked at compile time
ClonevsCopy— explicit deep-duplicate vs implicit bitwise- When to
clone()? — freely while learning; optimize after profiling (correctness first)
The deeper insight worth stating in any Rust-adjacent conversation: ownership isn't really about memory. It's a general discipline for reasoning about who can change what, when — which is why it also governs thread safety, why Rust needs no garbage collector, and why the language keeps winning "most admired" surveys despite the learning curve. Fight the compiler for two weeks; after that, it writes better programs than your habits did.
Related: data structure selection · complexity analysis · the study plan