One value has one current owner. Ownership is responsibility, not mere visibility.
Chapter 10: Ownership, First Contact
Prerequisites
You will understand
- The three ownership rules
- Why assignment moves, not copies
- How scope triggers
drop
Reading time
Ownership as the Library Checkout Card
`String` Ownership on Stack and Heap
#![allow(unused)]
fn main() {
let s1 = String::from("hello");
let s2 = s1;
// println!("{s1}"); // ERROR
println!("{s2}");
}
s1 owns heap data. Stack stores ptr + len + cap.
s1 invalidated.
s1 no longer has authority.
s2 is the sole owner. Drops on scope exit.
Passing to a Function Moves Ownership
Assignment of non-`Copy` values moves that responsibility unless the API says otherwise.
When the owner leaves scope, `Drop` runs exactly once. That is the invariant the compiler is protecting.
Why This Matters
Ownership is Rust’s defining feature. Before learning how to satisfy the borrow checker, you must understand why it exists. Systems languages typically make you choose between manual memory management (fast but error-prone, like C) or garbage collection (safe but unpredictable, like Java or Go).
Rust chooses a third path: Ownership. By enforcing strict rules at compile time about who is responsible for a piece of data, Rust guarantees memory safety without needing a runtime garbage collector. This is the foundation of Rust’s “fearless concurrency” and zero-cost abstractions.
Mental Model: The Checkout Card
Think of ownership like a library checkout card for a rare, one-of-a-kind book.
- One Owner: Only the person whose name is on the card is responsible for the book.
- Move: If you give the book to a friend, you must also hand over the checkout card. You are no longer responsible for it, and the library will not accept it from you.
- Drop: When the person holding the card leaves town (goes out of scope), they must return the book to the library.
In Your Language: Ownership vs Garbage Collection
#![allow(unused)]
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 MOVED, now invalid
// s1 can't be used here
drop(s2); // freed deterministically
}
s1 = "hello"
s2 = s1 # both point to same object
print(s1) # still works — refcount = 2
del s2 # refcount = 1, not freed yet
# GC decides when to free (non-deterministic)
Walk Through: What Happens During a Move
let s1 = String::from("hello"); — The allocator places "hello" on the heap. s1's stack frame stores pointer, length (5), and capacity (5). s1 is the sole owner.
let s2 = s1; — The 24 bytes of stack metadata (ptr, len, cap) are copied into s2's stack slot. The compiler marks s1 as uninitialized. The heap allocation is NOT duplicated.
s2 leaves scope, Drop::drop runs, freeing the heap buffer. Because only s2 is live, exactly one free() call happens. No double-free, no leak.
Step 1 - The Problem
Learning Objective By the end of this step, you should be able to explain the core problem ownership solves: tracking responsibility for dynamically allocated memory without a garbage collector.
Step 2 - The Heap and the Stack
To understand ownership, you must understand where data lives.
- The Stack: Fast, fixed-size, strictly ordered (Last In, First Out). Local variables go here. When a function ends, its stack frame is instantly popped.
- The Heap: Slower, dynamic size, unordered. You request space from the OS, and it gives you a pointer.
If you have a String (which can grow), the characters live on the heap. But the pointer to those characters, along with the length and capacity, lives on the stack.
When the stack variable goes out of scope, who cleans up the heap data? That is the problem ownership solves.
Real-World Pattern
When you contribute to large Rust codebases (like Tokio or Serde), you will see very few calls to raw allocation or deallocation. Instead, you see types like Box, Vec, and String managing memory internally. Because ownership is strict, contributors do not need to guess if a function will free memory they pass to it. If the function takes a value (not a reference), it takes responsibility.
Step 3 - Practice
Code Reading Drill
Consider this snippet:
#![allow(unused)]
fn main() {
let my_string = String::from("Rust");
let s2 = my_string;
}
Who is responsible for the string data after line 2 executes?
Error Interpretation
If you try to use my_string after the snippet above, rustc will give you error E0382: use of moved value. This is the compiler telling you that the authority to read or modify the string has transferred to s2.
Compiler Error Decoder - Ownership Basics
Use this table while reading compiler output. The goal is to map each error to the ownership rule that was violated.
| Error code | What it usually means | Typical fix direction |
|---|---|---|
| E0382 | You used a value after it was moved | Borrow with &T, return ownership, or clone intentionally |
| E0507 | You tried to move out of borrowed content | Borrow inner data, restructure ownership, or use mem::take where appropriate |
| E0716 | A temporary value was dropped while still borrowed | Bind temporary values to named variables before borrowing |
If you still feel stuck, jump to rustc --explain <CODE> and connect the explanation back to the ownership timeline in this chapter.
Chapter Resources
- Official Source: The Rust Programming Language, Chapter 4: Understanding Ownership
- Official Source: Rustonomicon: Ownership