Ownership & Borrowing
Understand Rust's defining feature: the ownership rules, move semantics, the Clone trait, and how borrowing with references lets you access data without taking ownership.
Introduction
Ownership is the single feature that makes Rust different from almost every other language. It is a set of rules, checked entirely at compile time, that lets Rust guarantee memory safety without a garbage collector and without manual `free()` calls.
This is the most important lesson in the course — take your time here. Every later lesson, from structs to error handling, assumes you understand how ownership and borrowing work.
- The three core ownership rules that govern every value in Rust.
- What a "move" is, and why assigning a String doesn't copy it.
- The Copy and Clone traits, and when each applies.
- How borrowing with `&` lets you access data without taking ownership.
- The borrow checker's rule: many readers, or one writer — never both at once.
The Three Ownership Rules
Rust's entire memory model rests on three simple rules, enforced by the compiler at compile time, not at runtime.
- Each value in Rust has exactly one owner — the variable that holds it.
- There can only be one owner at a time — ownership can move from one variable to another, but never be shared by two variables at once.
- When the owner goes out of scope, Rust automatically drops the value and frees its memory — no manual deallocation needed.
Move Semantics
For heap-allocated types like `String`, assigning one variable to another does not copy the data — it moves ownership. After a move, the original variable is no longer valid, and the compiler will refuse to let you use it.
fn main() { let s1 = String::from("hello"); let s2 = s1; // s1's data is moved into s2; s1 is no longer valid here
println!("{}", s2);}Click Run to see what this code prints.
If you uncommented a line trying to print `s1` after this move, the compiler would reject the program with a "value borrowed after move" error. This is not a runtime crash — it's a compile-time guarantee that you can never accidentally use invalidated data.
The Copy and Clone Traits
Not everything moves. Simple, fixed-size, stack-only types like integers, `bool`, `char`, and `f64` implement the `Copy` trait, meaning assignment copies the value instead of moving it — both variables remain valid afterward, since duplicating them is cheap and has no ownership implications.
For heap-allocated types, you can explicitly opt into a deep copy using `.clone()`. This duplicates the actual data, giving you two fully independent owners — at the cost of extra memory allocation and CPU time, which is why Rust never does it implicitly.
Borrowing with References
Constantly moving or cloning values just to pass them to a function would be exhausting. Instead, Rust lets you "borrow" a value with a reference (`&`), giving a function temporary access without transferring ownership.
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);}
fn calculate_length(s: &String) -> usize { s.len()}Click Run to see what this code prints.
Because `calculate_length` receives `&s1` (a reference) instead of `s1` itself, ownership never leaves `main`. `s1` is still perfectly valid to use after the function call — something that would be impossible if we had passed `s1` by value.
Mutable References
A regular `&` reference is read-only. To let a borrowed function modify the original value, you use a mutable reference, `&mut`.
fn main() { let mut s = String::from("hello"); change(&mut s); println!("{}", s);}
fn change(s: &mut String) { s.push_str(", world");}Click Run to see what this code prints.
Note that `s` itself must be declared `mut` for you to be able to create a mutable reference to it in the first place — mutability has to be explicit all the way through.
The Borrow Checker's Rules
The borrow checker enforces one central rule for every value, within any given scope: you may have either any number of immutable references (`&T`), or exactly one mutable reference (`&mut T`) — never both kinds at the same time. This rule alone is what makes Rust's "fearless concurrency" possible: it is the same rule the compiler uses to rule out data races between threads.
The borrow checker also guarantees references are always valid — Rust will refuse to compile a function that tries to return a reference to a value created inside that function, because the value would be dropped (and the reference left dangling) the moment the function returns.
Move vs Borrow vs Clone
| Concept | What Happens | When to Use |
|---|---|---|
| Move | Ownership transfers to the new variable; the original binding becomes invalid. | The default behavior when assigning heap-allocated types like String or Vec. |
| Borrow (&T) | You get a reference to the value without taking ownership. | Reading data without needing to own or modify it. |
| Mutable Borrow (&mut T) | You get exclusive, temporary permission to modify the value through a reference. | Modifying data in place without transferring ownership. |
| Clone (.clone()) | A full deep copy of the data is made; both variables own independent data. | When you genuinely need two independent owners, at the cost of extra memory and CPU. |
Common Mistakes
- Trying to use a value after it has been moved (`let s2 = s1;` then using `s1` again) — the compiler rejects this with a "value borrowed after move" error.
- Attempting to create a mutable borrow while an immutable borrow of the same value is still active — the compiler enforces many readers OR one writer, never both.
- Reaching for `.clone()` every time the borrow checker complains, instead of restructuring code to borrow correctly — this hides real performance costs and misses the point of ownership.
- Trying to return a reference to a value created inside the function — the value is dropped when the function ends, so Rust refuses to compile a dangling reference.
Best Practices
- Prefer borrowing (`&T` or `&mut T`) over cloning whenever a function only needs to read or briefly modify data.
- Keep mutable borrows short-lived and narrowly scoped so the borrow checker can easily prove your code is safe.
- Treat "fighting the borrow checker" as a signal to reconsider your data's ownership design, not just a wall to clone your way past.
- Read compiler error messages closely — Rust's borrow-checker diagnostics usually explain exactly which rule was violated and suggest a concrete fix.
Frequently Asked Questions
The ownership and borrowing rules, enforced entirely at compile time, give the same memory safety a garbage collector provides, but with zero runtime overhead and no unpredictable GC pauses.
Borrowing gives temporary access to existing data without owning it; cloning creates an entirely new, independent copy of the data.
No. Rust enforces at most one mutable reference, or any number of immutable references, within the same scope — never both at the same time.
Not immediately — the compiler infers most lifetimes automatically. Explicit lifetime annotations only become necessary in more advanced scenarios you'll encounter later.
Key Takeaways
- Every value has exactly one owner; when that owner goes out of scope, the value is automatically dropped.
- Assigning heap-allocated types moves ownership by default; the original variable becomes invalid.
- Copy types (integers, bool, char) duplicate cheaply on assignment; heap types need an explicit `.clone()` for a deep copy.
- References (`&T`, `&mut T`) let you borrow data without taking ownership.
- The borrow checker allows many immutable borrows, or exactly one mutable borrow, never both at once.
Summary
You've now covered Rust's single most important concept: ownership, moves, cloning, and borrowing. Everything else in this course builds on this foundation. Next, you'll use structs to model your own data types, and see ownership and borrowing in action with methods.