Rust's ownership system is what makes it unique. It guarantees memory safety at compile time without a garbage collector — no runtime pauses, no dangling pointers, no use-after-free bugs. But it takes some getting used to.
The three rules of ownership
1. Every value has exactly one owner 2. When the owner goes out of scope, the value is dropped (memory freed) 3. Ownership can be transferred (moved) or borrowed
Rule 1: One owner
let s1 = String::from("hello");
let s2 = s1; // s1 is MOVED to s2
// println!("{}", s1); // ERROR: s1 no longer valid
println!("{}", s2); // OKWhen you assign s1 to s2, the String's ownership moves to s2. s1 is no longer valid. This prevents two variables from freeing the same memory (double-free).
Rule 2: Drop when out of scope
{
let s = String::from("hello");
// s is valid here
} // s goes out of scope, memory is freed
// s is goneNo garbage collector needed — Rust knows exactly when to free memory.
Borrowing: using without owning
To use a value without taking ownership, borrow it with &:
let s1 = String::from("hello");
let len = calculate_length(&s1); // borrow s1
println!("{} has {} characters", s1, len); // s1 still valid
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope but doesn't own the string, so nothing is freedMutable borrows
You can borrow mutably, but only one mutable reference exists at a time:
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // ERROR: can't have two mutable borrows
r1.push_str(", world");
println!("{}", s);This prevents data races at compile time.
Why this matters
In C++, you can have two pointers to the same memory. One frees it; the other still points there — undefined behaviour. In Python, the garbage collector handles it but adds runtime overhead. Rust's ownership eliminates both problems: no dangling pointers (ruled out by ownership), no garbage collector pauses (memory freed deterministically at end of scope).
The borrow checker — the part of the compiler that enforces these rules — feels restrictive at first. But every error it catches is a memory bug that would have been silent and dangerous in C or C++.
Fighting the borrow checker is a normal part of learning Rust. After a few weeks it starts to feel natural, and then you start seeing memory bugs everywhere in languages that don't have it.