Ownership Model
TL;DR Every value has exactly one owner. Ownership transfers on move. The owner is responsible for dropping the value when it leaves scope — no GC, no manual free.
The Three Rules
- Every value has exactly one owner at any given time
- There can only be one owner at a time
- When the owner goes out of scope, the value is dropped
let s1 = String::from("hello");
let s2 = s1; // move: s1 is now invalid
// println!("{s1}"); // compile error: value borrowed after move
println!("{s2}"); // ok
Copy vs Move
Types that fit on the stack and are cheap to duplicate implement Copy. Assignment copies instead of moves.
let x: i32 = 5;
let y = x; // copy — both x and y are valid
println!("{x} {y}");
let s = String::from("hello");
let t = s; // move — s is invalidated
// println!("{s}"); // error
Copy types: all integer/float primitives, bool, char, (), tuples and arrays of Copy types.
!Copy types (move semantics): String, Vec<T>, Box<T>, anything that owns heap data.
Drop Order
Values are dropped in reverse declaration order. Inner scopes are dropped before outer scopes.
{
let a = String::from("a");
{
let b = String::from("b");
} // b dropped here
} // a dropped here
Struct fields drop in declaration order (top to bottom).
Ownership and Functions
Passing a value to a function moves it. The function becomes the new owner.
fn consume(s: String) {
println!("{s}");
} // s dropped here
let s = String::from("hello");
consume(s);
// s is invalid here — moved into the function
To use a value after passing it, either return it back, clone it, or pass a reference.
Gotchas
Partial moves invalidate the whole struct:
struct Pair { a: String, b: String }
let p = Pair { a: "hello".into(), b: "world".into() };
let a = p.a; // partial move
// println!("{}", p.b); // error: p is partially moved
Use ref bindings or restructure to avoid this.
Loop moves in closures:
let s = String::from("hello");
let f = || println!("{s}"); // s is captured by move if `move` is used
f();
Without move, closures capture by reference when possible.