Ownership & Borrowing

This is the idea that makes Rust different from every other language on this site. Every value has exactly one owner, and Rust enforces that at compile time — which is how it guarantees memory safety without a garbage collector running in the background.

The three ownership rules

Rust's whole memory model comes down to three rules the compiler checks for every value: each value has exactly one owner at a time; when the owner goes out of scope, the value is dropped (its memory freed) automatically; and ownership can be transferred (moved) to someone else, at which point the old owner can no longer use it.

Moves: assignment can invalidate the original

For simple types like i32, assigning one variable to another copies the value — both remain usable. For a String (heap-allocated, growable text), the same assignment instead moves ownership:

Rust src/main.rs
fn main() {
    let original = String::from("hello");
    let copy = original;

    println!("{}", original);
}
Compiler output
error[E0382]: borrow of moved value: `original`
 --> src/main.rs:5:20
  |
2 |     let original = String::from("hello");
  |         -------- move occurs because `original` has type `String`
3 |     let copy = original;
  |                -------- value moved here
4 |
5 |     println!("{}", original);
  |                    ^^^^^^^^ value borrowed here after move

This is not a runtime crash — it never compiles at all. let copy = original; transfers ownership of the string data to copy; original is now considered invalid, and the compiler refuses to let you use it again. This prevents a real class of C/C++ bugs: two variables both believing they own the same heap memory, and both trying to free it (a double-free) when they go out of scope.

If you genuinely want two independent copies, call .clone(), which duplicates the underlying data explicitly:

Rust src/main.rs
fn main() {
    let original = String::from("hello");
    let copy = original.clone();

    println!("{} {}", original, copy);
}
Output
hello hello

Borrowing: using a value without taking it

Passing a value into a function moves it by the same rule — which would make even printing a string inconvenient if that were the only option. A reference (&) lets code borrow a value temporarily without taking ownership:

Rust src/main.rs
fn print_length(s: &String) {
    println!("Length: {}", s.len());
}

fn main() {
    let text = String::from("hello");
    print_length(&text);
    println!("Still usable: {}", text);
}
Output
Length: 5
Still usable: hello

&text passes a reference rather than the value itself, so main still owns text after the call — nothing was moved. This is the pattern you'll reach for constantly: pass by reference when a function only needs to look at a value, and only move or explicitly clone when it genuinely needs to take ownership.

The borrowing rule: many readers, or one writer, never both

Rust src/main.rs — this won't compile
fn main() {
    let mut balance = 100;
    let r1 = &balance;
    let r2 = &mut balance;

    println!("{} {}", r1, r2);
}
Compiler output
error[E0502]: cannot borrow `balance` as mutable because it is
also borrowed as immutable
  |
3 |     let r1 = &balance;
  |              -------- immutable borrow occurs here
4 |     let r2 = &mut balance;
  |              ^^^^^^^^^^^^ mutable borrow occurs here
5 |
6 |     println!("{} {}", r1, r2);
  |                       -- immutable borrow later used here

Rust allows either any number of immutable references (&T) at once, or exactly one mutable reference (&mut T) — never both at the same time. This is the actual rule the borrow checker enforces, and it's what rules out an entire category of bugs where one piece of code reads a value while another is midway through changing it.

Why this is worth the friction: every borrow-checker error you hit while learning Rust is the compiler catching, at compile time, a bug that in C or C++ would have been a runtime crash, a data race, or a silent memory-corruption bug that only shows up much later, far from its actual cause. The rules feel strict at first; they're the entire reason Rust doesn't need a garbage collector to be memory-safe.