Ownership
Chapter 1 promised memory safety without a garbage collector. This chapter is where that promise becomes concrete — ownership is Rust's single defining idea, and nearly everything else in this course builds on it.
The Three Ownership Rules
- Each value has exactly one owner at a time.
- When the owner goes out of scope, the value is dropped (its memory freed) automatically.
- Ownership can be transferred (moved), but a value is never implicitly duplicated.
Every example in this chapter is really just these three rules playing out in different situations.
Stack vs. Heap, Briefly
Simple fixed-size types (i32, bool, char, tuples of these) live entirely on the stack — cheap to copy, since copying just means duplicating a few fixed bytes. A String (or Vec, covered in Chapter 8) stores its actual character data on the heap, with only a pointer, length, and capacity sitting on the stack. This distinction is exactly why move semantics matter: moving a String is cheap — it's just copying that small pointer/length/capacity trio, never the underlying heap data itself.
Move Semantics
Coming from Go or JavaScript, let s2 = s1 looks like it should leave both variables usable — either both reference the same underlying string, or the whole value gets copied. Rust does neither: it moves ownership of the heap data from s1 to s2, and s1 becomes invalid from that point on. Using it afterward is a compile error, not a runtime surprise.
Why Not Just Copy Everything?
The alternative — deep-copying on every assignment — would be safe, but expensive, and that cost would be invisible in the code. Rust instead makes moving the default (cheap, just a pointer/length/capacity copy) and requires an explicit .clone() call whenever a real, expensive deep copy is actually wanted — making that cost visible right in the source.
Simple stack-only types (i32, f64, bool, char, and tuples made entirely of these) implement Rust's Copy trait instead — copying them is so cheap that Rust just does it automatically on assignment, and neither variable becomes invalid:
One Owner at a Time & Automatic Cleanup
When a variable goes out of scope — the end of a block or function — Rust automatically calls drop on it, freeing its memory immediately and deterministically. This is the actual mechanism that eliminates the need for a garbage collector: because ownership rules guarantee, at compile time, that exactly one thing is ever responsible for a given piece of data, the compiler always knows the exact moment cleanup should happen — no background process needs to figure it out at runtime.
Ownership and Functions
Passing a value to a function moves it in (unless the type is Copy) — the function becomes the new owner, and the caller's variable becomes invalid after the call, unless the function explicitly hands ownership back by returning the value.
This is genuinely inconvenient if the caller just wanted the function to look at the value without giving it up entirely — which is exactly the problem Chapter 4's borrowing exists to solve.
| Move Types | Copy Types | |
|---|---|---|
| Examples | String, Vec<T>, most custom structs | i32, f64, bool, char, tuples of these |
| On assignment | Ownership moves; original becomes invalid | Value is copied; both remain valid |
| To get a real duplicate | Explicit .clone() | Automatic — no clone needed |
& and &mut) is specifically designed to solve the exact frustration this chapter's function example just demonstrated — letting a function use a value without taking ownership of it at all.
Coding Challenges
Write code that creates a String, moves it into a second variable, then attempts to print the original variable. Run cargo run, note the exact compiler error, then fix it using .clone() instead so both variables remain usable.
📄 View solutionExplain why let x = 5; let y = x; println!("{} {}", x, y); compiles fine with no error, while the equivalent pattern with a String does not — referencing the Copy trait specifically.
📄 View solutionWrite a function that takes a String, prints it, and returns it back to the caller so the caller's variable stays usable after the call — without using .clone() anywhere.
📄 View solutionChapter 3 Quick Reference
- Three rules: one owner at a time; drop happens automatically at scope end; a value is never implicitly duplicated
- Move — assigning/passing a heap-backed value (String, Vec) transfers ownership; the original variable becomes invalid
- .clone() — an explicit, real deep copy; makes the cost of copying visible in the code
- Copy trait — simple stack-only types (i32, f64, bool, char) copy automatically on assignment; neither variable is invalidated
- drop — called automatically when a variable's owner goes out of scope; deterministic, no garbage collector involved
- Passing a value to a function moves it in, unless the type is
Copyor the function returns it back - "Borrow of moved value" errors are normal and common — not a sign of doing something wrong
- Next chapter: Borrowing & References —
&and&mut, letting a function use a value without taking ownership