Lesson 6

Ownership

Understand Rust's most unique feature: ownership, move semantics, and the Copy trait.

The Big Idea

Ownership is Rust's secret weapon -- it's how Rust guarantees memory safety without a garbage collector. There are three rules:

  1. Each value has one owner (a variable).
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value is dropped (freed).

Move Semantics

When you assign a heap-allocated value (like a String) to another variable, ownership moves. The original variable becomes invalid -- you can't use it anymore. This prevents double-free bugs.

let s1 = String::from("hello");
let s2 = s1;  // s1 is MOVED to s2
// println!("{}", s1);  // ERROR: s1 is no longer valid

Copy Types

Simple types like integers, floats, and booleans implement the Copy trait. They're copied instead of moved, so both variables remain valid. Stack-only data is cheap to copy.

Run the code below. Then try uncommenting the marked line to see what the compiler says.

Try it

fn main() {
    // --- Integers are COPIED ---
    let a = 42;
    let b = a;  // a is copied, both are valid
    println!("a = {}, b = {}", a, b);

    // --- Strings are MOVED ---
    let s1 = String::from("hello");
    let s2 = s1;  // s1 is moved to s2
    println!("s2 = {}", s2);

    // Uncomment the next line to see a compile error:
    // println!("s1 = {}", s1);  // ERROR: value borrowed after move

    // --- Clone: explicit deep copy ---
    let s3 = String::from("world");
    let s4 = s3.clone();  // deep copy, both are valid
    println!("s3 = {}, s4 = {}", s3, s4);

    // --- Ownership and functions ---
    let name = String::from("Rust");
    take_ownership(name);
    // name is no longer valid here -- it was moved into the function

    let x = 10;
    make_copy(x);
    println!("x is still valid: {}", x);  // works because i32 is Copy

    // --- Getting ownership back ---
    let greeting = String::from("hi there");
    let greeting = give_and_take(greeting);
    println!("Got it back: {}", greeting);
}

fn take_ownership(s: String) {
    println!("I own: {}", s);
}  // s is dropped here

fn make_copy(n: i32) {
    println!("I got a copy: {}", n);
}

fn give_and_take(s: String) -> String {
    println!("Passing through: {}", s);
    s  // return ownership back to caller
}

Challenge

Try uncommenting the line that causes a compile error. Read the error message carefully -- Rust's compiler gives excellent explanations.

Previous: Control Flow

Next: References & Borrowing