Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 10: Ownership, First Contact

You will understand

  • The three ownership rules
  • Why assignment moves, not copies
  • How scope triggers drop

Reading time

30 min
+ 20 min exercises
Hero Illustration

Ownership as the Library Checkout Card

1. OWNER Person A holds the checkout card. 2. MOVE Responsibility moves with the card. 3. USE AFTER MOVE Old name still exists, authority does not. 4. DROP Scope ends. The book goes back exactly once.
The checkout card is the key detail. Rust ownership is not “who can see the book.” It is “who is responsible for it.” The name that loses the card is not allowed to act like the owner anymore.
Memory Diagram

`String` Ownership on Stack and Heap

STACK HEAP Step 1: s1 ptr: 0x1000 len: 5 cap: 5 Step 2: s1 MOVED invalid name Step 2: s2 ptr: 0x1000 len: 5 cap: 5 h e l l o assignment moves responsibility, not heap bytes DROP
The physical stack fields can be copied. The semantic event is still a move, because Rust treats those fields as the unique responsibility token for the heap allocation.
#![allow(unused)]
fn main() {
let s1 = String::from("hello");
let s2 = s1;
// println!("{s1}");   // ERROR
println!("{s2}");
}
Own created s1 owns heap data. Stack stores ptr + len + cap.
Move occurs s1 → s2. Stack repr copied. s1 invalidated.
E0382 Use of moved value. s1 no longer has authority.
Valid s2 is the sole owner. Drops on scope exit.
Ownership Flow

Passing to a Function Moves Ownership

main scope s = String::from(…) MOVE s fn takes_ownership(s) s is used inside fn DROP scope ends → heap freed s — INVALID After the call: • The function owned s • Drop ran when fn returned • main's binding is now dead Using s after this call produces E0382. value used after move
A function call that takes ownership is a one-way transfer. The caller gives up the value permanently. The only way to get it back is for the function to return it explicitly — there is no implicit "loan."
1

One value has one current owner. Ownership is responsibility, not mere visibility.

2

Assignment of non-`Copy` values moves that responsibility unless the API says otherwise.

3

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.

  1. One Owner: Only the person whose name is on the card is responsible for the book.
  2. 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.
  3. 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

Rust — ownership
#![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
}
Python — GC
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

Step 1: Allocation 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.
Step 2: Move 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.
Step 3: Drop When 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 codeWhat it usually meansTypical fix direction
E0382You used a value after it was movedBorrow with &T, return ownership, or clone intentionally
E0507You tried to move out of borrowed contentBorrow inner data, restructure ownership, or use mem::take where appropriate
E0716A temporary value was dropped while still borrowedBind 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