06. The Three Rules of Ownership + move/copy
Ownership is the mechanism by which Rust guarantees memory safety without a garbage collector. Three rules suffice — each value has exactly one owner, the value is dropped when its owner goes out of scope, and assignment / argument passing moves ownership. This lesson covers move vs. copy, the meaning of Drop, and leads naturally into borrowing.
What you'll learn
- 1State the three ownership rules in your own words
- 2Spot where a move happens in real code
- 3Distinguish Copy types from non-Copy types
- 4Know when Drop runs
- 5Read use-after-move compiler errors and locate the root cause
Overview
Most languages solve memory management one of two ways — **manual (C/C++ malloc/free)** or **automatic GC (Java/Python)**. Manual is fast but dangerous; GC is safe but adds STW pauses. Rust takes a third path — **the compiler enforces ownership rules**, giving you safety without a GC at runtime.
Core Concepts
1) The three rules
- Each value has **exactly one owner**
- Only one owner exists at any given time
- When the owner **goes out of scope**, the value is dropped automatically
2) move — transferring ownership
Assigning or passing a value that owns heap data (String, Vec, ...) **transfers ownership**. The original binding becomes invalid.
let s1 = String::from("hello");
let s2 = s1; // s1's ownership moves to s2
println!("{}", s1); // error[E0382]: borrow of moved value: `s1`3) Copy — types where bitwise copy is safe
Types that **live only on the stack** (i32, f64, bool, char, fixed-size arrays of Copy types) implement the Copy trait — assignment produces a duplicate, not a move.
let x = 5;
let y = x; // copy, not move
println!("{} {}", x, y); // both alive4) Drop — destruction hook
When the owner goes out of scope, `Drop::drop` runs automatically, releasing heap memory, file handles, sockets, etc. This pattern is **RAII** (Resource Acquisition Is Initialization).
| Type | Assignment | Why |
|---|---|---|
| i32, bool, char | Copy | Stack only, bitwise safe |
| String, Vec<T> | Move | Heap pointer — two owners → double-free |
| &T (reference) | Copy | Borrowing, no ownership transfer |
| (i32, i32) | Copy | All components are Copy |
| (i32, String) | Move | String pulls the whole tuple into Move |
Hands-on Examples
Passing an argument moves ownership:
fn takes_ownership(s: String) {
println!("{}", s);
} // s is dropped here
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s); // error — s has been moved
}Hand it back to keep using it (borrowing is nicer — this is for contrast):
fn take_and_give(s: String) -> String {
println!("len={}", s.len());
s
}
fn main() {
let s1 = String::from("hello");
let s2 = take_and_give(s1);
println!("{}", s2); // OK — s2 is the new owner
}Observe Drop firing:
struct Resource { name: String }
impl Drop for Resource {
fn drop(&mut self) { println!("drop: {}", self.name); }
}
fn main() {
let _r = Resource { name: "file.txt".into() };
println!("end of main");
} // prints "end of main" then "drop: file.txt"Common Mistakes
Q. After passing a String to a function I want to use it again — do I always return it?
A. That's exactly what **borrowing (`&`)** solves in the next lesson — lend without transferring ownership. For now, get the move mechanic locked in.
Q. `let s2 = s1.clone()` leaves both alive — what's that?
A. `.clone()` is an **explicit deep copy** — allocates a new heap buffer. You pay memory / time cost for two independent owners.
Q. How do I make my own struct Copy?
A. Add `#[derive(Copy, Clone)]`. Only valid if **every field is Copy**. A struct containing a String can't be Copy.
Recap
- Each value has one owner; end of scope = drop
- Assigning or passing heap-owning values = move, original invalidated
- Stack-only types are Copy, so assignment duplicates
- Drop automates RAII-style resource release
Try It Yourself
- Move a String into a function, then try to use it after — note the compiler error
- Create two Resource structs and observe drop order at end of main (LIFO?)
- Derive Copy on a struct containing only i32, then try the same with a struct containing String
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗