RsRust · Lesson 3 of 8

Ownership & Borrowing

This is the chapter that makes people close their laptop and go for a walk. But it's also the chapter that makes Rust special. Take it slow.

Every value in Rust has exactly one owner. When the owner goes out of scope, the value is dropped (freed). No garbage collector. No manual free(). This is Rust's entire memory safety story.

Rust
fn main() {
    // Ownership basics
    let s1 = String::from("hello");
    let s2 = s1;  // s1 is MOVED to s2
    // println!("{}", s1);  // ERROR: s1's value moved to s2

    // Clone — explicit deep copy
    let s1 = String::from("hello");
    let s2 = s1.clone();   // s1 is still valid
    println!("{} {}", s1, s2);

    // Copy types (integers, floats, bool, char, tuples of copies)
    // are copied, not moved
    let x = 5;
    let y = x;   // x is COPIED, not moved
    println!("{} {}", x, y);   // both work fine

    // Move into a function
    let s = String::from("hello");
    takes_ownership(s);     // s is moved
    // println!("{}", s);   // ERROR: s was moved

    // Return ownership
    let s = gives_ownership();
    println!("{}", s);      // works
}

fn takes_ownership(s: String) {
    println!("{}", s);
}   // s dropped here, memory freed

fn gives_ownership() -> String {
    String::from("yours now")
}

References let you use a value without taking ownership. A reference is like a pointer, but the borrow checker guarantees it's always valid — no dangling pointers ever.

Rust
fn main() {
    let s = String::from("hello");

    // Shared reference — borrow without taking ownership
    let len = calculate_len(&s);   // &s is a reference to s
    println!("{} has length {}", s, len);   // s still works!

    // Mutable reference — allows mutation
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s);   // hello world

    // Rules:
    // 1. Any number of shared (&) references, OR
    // 2. Exactly ONE mutable (&mut) reference — never both
    // These rules prevent data races at compile time.

    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);  // OK — two shared refs
    // let r3 = &mut s;            // ERROR — can't have mutable while shared refs exist
}

fn calculate_len(s: &String) -> usize {
    s.len()
}   // s is NOT dropped, we only borrowed it

fn change(s: &mut String) {
    s.push_str(" world");
}
◆ Note
The borrow checker operates at compile time with zero runtime cost. If your code compiles, memory safety is guaranteed. If it doesn't compile, the error messages will tell you exactly what the problem is (usually).