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

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

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

References