Rust Ownership & Borrowing
Ownership is the idea that makes Rust different. Every value has exactly one owner; when the owner goes out of scope, the value is dropped and its memory freed. You can borrow a value (take a reference to it) without taking ownership, under rules the compiler enforces: many readers or one writer, never both, and no reference may outlive the value it points to.
Those rules give Rust memory safety without a garbage collector. Use-after-free, double frees, dangling pointers, and data races are compile errors rather than production incidents. The price is a learning curve: the borrow checker will reject code that would be fine in most languages, until you learn to structure data the way it expects.
TL;DR
- Each value has one owner; when the owner goes out of scope, the value is dropped.
- Assigning or passing a non-
Copyvalue moves it: the old binding can no longer be used. - Borrowing:
&Tis a shared (read-only) reference;&mut Tis a mutable, exclusive one. - The rule: any number of
&Tor exactly one&mut Tat a time, and references can't outlive their referent. - Lifetimes (
'a) name how long references are valid; usually they're inferred. - For shared ownership use
Rc<T>(single-thread) orArc<T>(multi-thread); for interior mutability,RefCell,Mutex, orRwLock.
Quick Example
Core Concepts
Ownership and Drop
When a variable that owns a value goes out of scope, Rust calls its destructor (Drop) and frees its resources: heap memory, file handles, sockets, locks. This is deterministic, like C++'s RAII: you know exactly when cleanup happens, with no GC pauses.
Moves vs Copies
Types that own heap data (String, Vec<T>, Box<T>) move on assignment: only one binding owns the buffer, so there's never a double free. Simple, fixed-size types that implement Copy (integers, floats, bool, char, and tuples or arrays of Copy types) are copied bitwise instead, and both bindings stay valid.
Clone is the explicit, possibly expensive deep copy: v.clone(). Rust never clones implicitly, so costs are visible in the code.
Borrowing Rules
The compiler tracks where each reference is last used (non-lexical lifetimes), not just its scope. The core rule prevents a whole class of bugs. For example, you can't push to a Vec while holding a reference to one of its elements, because the push might reallocate and leave the reference dangling.
Lifetimes
A lifetime is the region of code where a reference is valid. Most are inferred through elision rules. You annotate them when a function returns a reference and the compiler can't tell which input it came from:
'a says: the returned reference lives no longer than the shorter of the two inputs. Lifetimes don't change how long values live. They describe relationships so the compiler can verify them. 'static means "valid for the whole program", like string literals.
Structs that hold references need lifetime parameters too (struct Parser<'a> { input: &'a str }). Often the simpler design is for the struct to own its data instead.
Smart Pointers
Arc<Mutex<T>> is the classic pattern for state shared between threads or async tasks.
Working With the Borrow Checker
- Borrow in function parameters, own in struct fields. Functions take
&strand&[T]; long-lived structs ownStringandVec<T>. - Shorten borrows. Finish using a reference before mutating. Extracting a value into a local (
let n = v.len();) often resolves conflicts. - Use indices or IDs instead of references for graph-like data. Store nodes in a
Vecand refer to them by index, or use an arena. - Split borrows. Borrowing two different fields of a struct mutably at once is allowed; borrowing the whole struct twice isn't. Methods like
split_at_mutsplit slices safely. - Clone when it's cheap and clarifies code. Cloning a small string to escape a lifetime tangle is often the pragmatic choice.
Best Practices
Accept the Most General Borrow
Take &str instead of &String, and &[T] instead of &Vec<T>. Callers can then pass literals, slices, and owned values alike.
Prefer Ownership in Data Structures
Structs full of lifetime parameters are hard to use and propagate annotations everywhere. Own your data unless profiling shows the copies matter.
Reach for Rc/RefCell Deliberately
They're sometimes the right model (shared caches, observer lists, GUI trees), but RefCell moves borrow errors from compile time to runtime panics. Needing them everywhere suggests restructuring data ownership.
Common Mistakes
Using a Value After Moving It
Mutating a Collection While Iterating It
Returning a Reference to a Local
FAQ
Why does Rust need ownership instead of garbage collection?
Ownership gives memory safety with predictable performance: no GC pauses, no runtime overhead, deterministic cleanup of any resource. That makes Rust suitable for OS kernels, embedded firmware, game engines, and latency-sensitive services where a garbage collector is unacceptable.
What's the difference between Copy and Clone?
Copy is an implicit, cheap bitwise copy for simple stack-only types; assignment copies instead of moving. Clone is an explicit .clone() call that may do arbitrary work, such as allocating and copying a heap buffer. Every Copy type is also Clone.
Do I need to write lifetimes often?
Less than you might fear. Elision handles most function signatures. You'll write them mainly for functions returning references derived from several inputs, and for structs holding references. Designing structs to own their data avoids most annotations.
When should I use Rc vs Arc?
Rc when sharing within a single thread; it's cheaper because its counter isn't atomic. Arc when sharing across threads or async tasks. The compiler enforces this: Rc isn't Send, so it can't cross thread boundaries.
Related Topics
- Rust — The language overview
- Rust Traits —
Copy,Clone,Drop, andSend/Sync - Rust Async — Ownership across
.awaitand'statictasks - Memory Management — Manual, GC, and ownership models compared
- Systems Programming — Where ownership pays off most