Ownership Basics
Rust's core idea — every value has exactly one owner — and what happens to memory when that owner goes out of scope.
3 min read
Ownership is the idea that makes Rust different from every language you've likely used before. It's how Rust manages memory without a garbage collector and without manual free() calls, and it's enforced entirely at compile time by a set of rules the compiler checks for you.
The three rules
- Every value has exactly one owner — the variable bound to it.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped (its memory is freed) automatically.
fn main() {
{
let s = String::from("hello"); // s owns this String
println!("{s}");
} // s goes out of scope here — its memory is freed automatically
}No garbage collector scanned for unreachable memory, and you never called free. The compiler inserted the cleanup at the closing brace because it knows exactly when s's owner disappears.
Moves: why this fails
Here's the example that trips up almost everyone coming from another language:
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{s1}"); // error: value borrowed here after move
}In a language like JavaScript or Python, let s2 = s1 copies a reference, and both names remain valid. In Rust, that same line moves ownership of the string from s1 to s2. s1 is no longer valid — not a copy, not a shared reference, just gone. This is deliberate: String owns a heap allocation, and if both s1 and s2 were considered valid owners, they'd both try to free the same memory when they went out of scope — a double-free bug. Rust prevents this at compile time by simply not letting s1 be used again.
Fix it by either cloning (an explicit, deliberate deep copy) or by borrowing, which the next lesson covers:
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // s1 and s2 are independent copies
println!("{s1} and {s2}"); // both valid
}Why simple types don't move
This works fine, with no move error:
fn main() {
let x = 5;
let y = x;
println!("{x} and {y}"); // both valid
}Types like integers, floats, bool, and char implement a special trait called Copy. Because they're small, fixed-size, and live entirely on the stack, Rust just duplicates the bits instead of moving ownership — cheap enough that there's no reason not to. String, Vec, and most other heap-allocating types don't implement Copy, precisely because copying them isn't free.
Ownership and functions
Passing a value to a function follows the same move rules:
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 was moved into the function
}Once s is passed in, main no longer owns it — the function does, and it's dropped when the function ends. This is the exact problem the next lesson, borrowing, exists to solve: letting a function use a value without taking ownership of it.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.