The secret to Rust's ability to ensure memory safety without a garbage collector is ownership. Every value has exactly one "owner", and the value is freed immediately when the owner goes out of scope. If you learn the three rules in this section, you will be much better at reading all compiler errors in the future.
Learning Objectives
- Memorize the three rules of ownership and identify them in code.
- Know the difference between stack and heap, and know which types are stored where.
- Distinguish between
Copytypes and non-Copytypes (e.g.String). - When a value is passed to a function, it tracks how ownership moves.
Key concepts
- Every value in Rust has a variable called owner.
- There is only an owner at any one time.
- When the owner goes out of scope, the value is droped immediately.
With just these three rules, memory bugs like use-after-free and double-free are blocked at the compilation stage.
- Stack: A value with a fixed size (i32, bool, char, fixed tuple/array, etc.). Fast and automatically released.
- Heap: Values with variable or dynamic size (
String,Vec<T>,Box<T>). There are allocation and release costs.
String places the actual character data on the heap, and only the metadata of (pointer, length, capacity) is placed on the stack.
Non-Copy types such as String ownership transfers in assignments and function calls. The moved variable cannot be used from that point on.
let s1 = String::from("hello");
let s2 = s1; //Move s1 → s2
//println!("{s1}"); // ❌ No longer valid
The scalar types (i32, f64, bool, char, and their own tuple/array) implement the Copy trait. When assigning or transferring, only bit copy occurs and the original can be used as is.
| Type example | move/copy | Reason |
|---|---|---|
i32, f64, bool, char |
copy | Fixed size/stack storage |
(i32, i32), [u8; 4] |
copy | All elements Copy |
String, Vec<T>, Box<T> |
move | owning heap resources |
When you pass a value as an argument to a function, the parameter becomes the new owner. If the caller wants to continue using the same value, (a) return it as a return value or (b) use reference in the next section.
See as example
//Part 6 Example 1: Moving ownership of String (move)
//
//For types that have data on the heap (String, Vec, Box, etc.), the
//value is "moved" during assignment or function calls. The moved variable is no longer available.
fn main() {
let s1 = String::from("hello"); //s1 is the owner of this heap data
let s2 = s1; //Ownership is moved to s2
//println!("{s1}"); // ❌ Compilation error: s1 is no longer valid
println!("s2 = {s2}");
let s3 = String::from("Rust");
take(s3); //When passed to a function, ownership moves to the argument
//println!("{s3}"); // ❌ s3 is also no longer valid
println!("When the scope ends, the owner is dropped and the memory is released.");
}
fn take(s: String) {
println!("Value received by take: {s}");
//When the function ends, s is dropped.
}
//Part 6 Example 2: A type that implements the Copy trait — operates as a copy
//
//A scalar type (i32, f64, bool, char, ...) and a tuple consisting of only them is `Copy`.
//Since bit copying occurs during assignment and transfer, it does not look like “moving”.
fn main() {
let a = 10;
let b = a; //The bit of a is moved to b. Copy
println!("a = {a}, b = {b}"); //a is also still available
let p = (1, 2); //(i32, i32) also after calling Copy
let q = p;
println!("p = {:?}, q = {:?}", p, q);
show(a);
println!("show a = {a}");
}
fn show(n: i32) {
println!("show: {n}");
}
//Part 6 Example 3: Reclaiming ownership with return value When you pass a String to the
//
//function, ownership moves. If you want to write it again, you can return it as the return value of
//, or use a "reference" that you will learn in the next part.
fn main() {
let s = String::from("hello");
let s = grow(s); //Pass s and bundle the new returned String back into s (shadowing)
println!("Result: {s}");
}
fn grow(mut s: String) -> String {
s.push_str(", world!");
s //Return ownership to the caller
}
Common Mistakes
A. There are two ways. (1) Make the function return the same String again, or (2) Lend it as a reference (&String or &str). Usually (2) is natural — we'll learn in the next section.
A. The compiler determines whether the Copy trait is implemented in the type. i32 is not Copy or String. It is the same syntax, but operates differently depending on the type.
A. Explicitly calling s1.clone() creates a new String that replicates the heap data as well. It's expensive, so use it only when you really need it.
A. This is the moment when the variable leaves the scope { ... }. For types that implement the Drop trait (String, Vec, Box, etc.), the resource release code is automatically called at this time. You can also explicitly drop it early with the drop(value) function.
Cleanup
- Every value has only one owner, and is dropped when the owner goes out of scope.
- Heap resource types (
String,Vec,Box) are moved when assigned or passed. - The scalar type implements
Copyand operates as a copy. - To rewrite the value passed to the function, return it or use the reference in the next section.
Try it yourself
Solve the assignment below. The reference solution is shown below.