The Ownership Rules
Rust's entire memory-safety guarantee rests on three foundational ownership rules enforced at compile time by rustc:
- Every value in Rust has an owner (a variable binding, struct field, or collection element that holds it).
- There can only be one owner at a time (transferring ownership to a new binding immediately invalidates the old binding).
- When the owner goes out of scope, the value is dropped (its destructor runs and its memory is freed automatically).
Visualizing the Three Rules
Rule 1: Every value has an owner
let s1 = String::from("data"); --> `s1` owns the heap buffer
Rule 2: Only one owner at a time
let s2 = s1; --> `s2` is now the sole owner; `s1` is invalidated!
Rule 3: When the owner goes out of scope, the value is dropped
} --> `s2` goes out of scope at `}`; heap buffer is freed once!
Rule 1: Every Value Has an Owner
Whenever a value is created in Rust, it is either bound to a variable (let x = ...;), stored inside an owning container (such as a Vec<T>, tuple, or struct field), or held in an unnamed temporary slot for the duration of the enclosing expression or statement:
struct DropLogger(&'static str);
impl Drop for DropLogger {
fn drop(&mut self) {
println!(" [Drop] {}", self.0);
}
}
fn main() {
println!("1. Creating a named owner:");
let _named = DropLogger("named variable (lives until end of main)");
println!("2. Creating an unnamed temporary value:");
DropLogger("unnamed temporary (dropped at the `;` on this line!)");
println!("3. Still inside main before closing brace...");
}
1. Creating a named owner:
2. Creating an unnamed temporary value:
[Drop] unnamed temporary (dropped at the `;` on this line!)
3. Still inside main before closing brace...
[Drop] named variable (lives until end of main)
Notice that even a value that is not assigned to a let variable has a well-defined owner (a temporary stack slot) that drops the value at the semicolon terminating that statement.
Rule 2: There Can Only Be One Owner at a Time
For any type that manages heap memory or system resources (does not implement Copy), assigning the variable to another variable, passing it by value to a function, or putting it into a struct or collection transfers ownership (performs a move) to the new owner:
fn take_ownership(received: String) {
println!("Inside take_ownership: {received}");
} // `received` is the sole owner here, so the String is dropped when `take_ownership` exits!
fn main() {
let first_owner = String::from("exclusive resource");
// 1. Move ownership from `first_owner` to `second_owner`:
let second_owner = first_owner;
// `first_owner` is no longer valid here!
// 2. Move ownership from `second_owner` into parameter `received`:
take_ownership(second_owner);
// `second_owner` is no longer valid here!
}
Inside take_ownership: exclusive resource
Attempting to read first_owner after moving it to second_owner violates Rule 2 and is rejected at compile time (E0382):
// ❌ does not compile
fn main() {
let first_owner = String::from("exclusive resource");
let second_owner = first_owner;
println!("{first_owner} and {second_owner}");
}
error[E0382]: borrow of moved value: `first_owner`
--> src/main.rs:5:15
|
2 | let first_owner = String::from("exclusive resource");
| ----------- move occurs because `first_owner` has type `String`, which does not implement the `Copy` trait
3 | let second_owner = first_owner;
| ----------- value moved here
4 |
5 | println!("{first_owner} and {second_owner}");
| ^^^^^^^^^^^^^ value borrowed here after move
error: aborting due to 1 previous error
Rule 3: When the Owner Goes Out of Scope, the Value Is Dropped
A variable's scope begins where it is declared and ends at the closing brace } of its enclosing block. If the variable still owns its value when execution leaves that block (via normal fallthrough, return, break, continue, or panic unwinding), the compiler automatically calls Drop::drop on the value:
- Variables in the same block drop in reverse declaration order (Last-In, First-Out).
- Struct fields and tuple elements drop in declaration order (first field drops first) when their parent struct or tuple is dropped.
- Variables whose ownership was already moved elsewhere are not dropped when their block ends, because the new owner is now responsible for dropping the value.
struct Item(&'static str);
impl Drop for Item {
fn drop(&mut self) {
println!(" [Drop] {}", self.0);
}
}
fn main() {
let kept_in_main = Item("1. kept_in_main");
let moved_into_inner = Item("2. moved_into_inner");
{
let inner_only = Item("3. inner_only");
let _took_ownership = moved_into_inner; // Ownership moves into the inner block!
let _ = inner_only;
println!("--- Exiting inner block ---");
} // Both `_took_ownership` ("2. moved_into_inner") and `inner_only` drop here!
let _ = kept_in_main;
println!("--- Exiting main ---");
} // Only `kept_in_main` drops here!
--- Exiting inner block ---
[Drop] 2. moved_into_inner
[Drop] 3. inner_only
--- Exiting main ---
[Drop] 1. kept_in_main
Summary of How the Three Rules Interact
| Operation | Effect on Old Owner | Effect on New Target | Who Drops the Value? |
|---|---|---|---|
let b = a; (Non-Copy type) |
a becomes uninitialized (moved) |
b becomes sole owner |
b when b goes out of scope |
let b = a; (Copy type, e.g., i32) |
a owns original bitwise value |
b owns independent bitwise copy |
Both are trivial stack values (no heap drop) |
let b = a.clone(); |
a retains ownership of original |
b owns newly duplicated buffer |
Both a and b drop their own buffers independently |
let r = &a; (Borrowing) |
a retains ownership |
r borrows read-only access |
a when a goes out of scope |