An Introduction to Rust · Part 2 of 3

Ownership, moves, and borrows

Rust’s memory model fits in three sentences:

  1. Every value has exactly one owner.
  2. When the owner goes out of scope, the value is dropped (freed).
  3. Ownership can be moved, or the value can be borrowed — but the borrows have rules.

That’s it. No garbage collector pausing your program, no free() to forget. The compiler tracks owners the way a type checker tracks types.

Moves

Assignment of heap-owning types transfers ownership:

let a = String::from("hello");
let b = a;                  // ownership moves to b
// println!("{a}");         // error: value borrowed after move

If you actually want two copies, say so explicitly with a.clone(). Rust makes the expensive thing visible instead of doing it silently.

Borrows

Most of the time you don’t want to give a value away — you just want to let a function look at it. That’s a reference, written &:

fn shout(text: &String) -> String {
    text.to_uppercase()
}

let word = String::from("hello");
let loud = shout(&word);    // word is borrowed...
println!("{word} -> {loud}"); // ...and still usable here

Borrows come in two flavors, and the rules are asymmetric on purpose:

  • Any number of shared borrows (&T) may exist at once — readers don’t conflict.
  • A mutable borrow (&mut T) must be exclusive — one writer, and no readers while it lives.
let mut nums = vec![1, 2, 3];
let first = &nums[0];       // shared borrow
// nums.push(4);            // error: can't mutate while borrowed
println!("{first}");        // borrow ends here
nums.push(4);               // now it's fine

That push error is the borrow checker preventing a real bug: pushing might reallocate the vector, leaving first pointing at freed memory. In C++ this compiles and corrupts; in Rust it doesn’t compile.

The discipline feels strict for a week. Then you notice you’ve stopped thinking about a whole category of bugs, and it starts feeling like a seatbelt. Next: how structs and enums let you model data precisely.