Ownership & Borrowing
The Ownership Rules
Three rules drive everything. Memorize them — every borrow-checker error traces back to one of these:
- Each value in Rust has a single owner.
- When the owner goes out of scope, the value is dropped (its memory freed).
- A value can be owned by exactly one binding at a time; moving it away invalidates the old binding.
fn main() {
// the String is owned by `s`; when `main` ends it is freed automatically
let s = String::from("hello");
println!("{s}");
} // `s` drops here — no free(), no GC, no leak
Move Semantics
Assigning an owned value transfers ownership. The original binding is then unusable — using it afterwards is a compile error, not undefined behavior:
Figure 1 — assigning s1 to s2 moves the value: s1 is invalidated and s2 owns it.
fn main() {
let s1 = String::from("hello");
let s2 = s1; // ownership moves to s2
// println!("{s1}"); // ERROR: s1 was moved and is now invalid
println!("{s2}"); // ok
}
Types whose values are cheap to copy bit-for-bit implement Copy (integers, floats, bool, char, and tuples of them). Those simply copy on assignment instead of moving:
fn main() {
let x = 5;
let y = x; // i32 is Copy: x stays valid
println!("{x} {y}"); // both work — no move happens
}
Borrowing
Using a value without taking ownership means borrowing it. A shared borrow &T lets many readers look at once; an exclusive borrow &mut T lets one writer change it. The borrow checker enforces "many readers or one writer":
Figure 2 — shared reads may coexist; an exclusive write excludes all others.
fn main() {
let mut s = String::from("hello");
let len = str_len(&s); // shared borrow: read only
println!("length = {len}");
append_world(&mut s); // exclusive borrow: may mutate
println!("{s}");
}
fn str_len(value: &String) -> usize { value.len() } // &T
fn append_world(value: &mut String) { value.push_str(" world"); } // &mut T
Mutable Borrowing & Aliasing
While an exclusive borrow is alive, no other borrow of the same value may exist. This rules out data races and iterator-invalidation bugs before the program runs:
fn main() {
let mut s = String::from("rust");
{
let r = &mut s; // one exclusive borrow
r.push('!');
} // borrow ends here
let r1 = &s; // now many shared borrows are fine
let r2 = &s;
println!("{r1} {r2}");
}
Slices
A slice (&str, &[T]) is a borrowed view of a contiguous range. It owns nothing, so functions can work on part of a value while the borrow rules stay simple:
fn main() {
let s = String::from("hello world");
let word = first_word(&s); // &str slice, borrows s
println!("first word: {word}");
}
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
println!("{s1}") after a move and read the error — the compiler points at the exact move site.