Chapter 2: Ownership

Remember the last time you debugged a memory leak? Maybe it was in JavaScript — an array that kept growing because you forgot to remove event listeners. Or in Python — objects piling up because circular references kept them alive past their usefulness.

Memory management is the problem every language tries to solve differently. Rust's answer is ownership.

The Three Rules

Rust's ownership system has three rules. Memorize them — they'll save your sanity:

  1. Each value in Rust has an owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value is dropped.

That's it. These three rules eliminate an entire category of bugs that have plagued programmers for decades.

What Does "Owner" Mean?

Let's look at some code:

fn main() {
    let s1 = String::from("hello");  // s1 owns the String
    let s2 = s1;                       // ownership moves to s2
    
    // This won't compile! s1 is no longer valid:
    // println!("{}", s1);  // ERROR: borrow of moved value
    
    println!("{}", s2);  // This works — s2 owns the String
}

When we write let s2 = s1, the ownership of the String moves from s1 to s2. After this line, s1 is invalid. You can't use it anymore. This isn't a runtime error — it's a compile-time error. Rust won't let you write code that uses a value after its owner has transferred it.

This is radical. Most languages let you pass references around freely. Rust forces you to think about who owns what, and when.

Why This Matters

Consider what happens in a function:

fn main() {
    let s = String::from("hello");
    
    takes_ownership(s);  // s's value moves into the function
    
    // This won't compile! s is no longer valid:
    // println!("{}", s);
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}  // some_string goes out of scope here, memory is freed

In languages like JavaScript or Python, the parameter would be a copy (or a reference that the garbage collector handles). In Rust, the ownership moves into the function. When the function ends, the value is dropped. If you still need s in main, you can't use it — and Rust tells you this at compile time, not at 3am in production.

Moving Instead of Copying

You might wonder: "But what if I want to keep both copies?"

Good question. Rust gives you two options:

Option 1: Clone the value

let s1 = String::from("hello");
let s2 = s1.clone();  // s1 and s2 are both valid, both own their own data

clone() creates a deep copy. Both variables now own separate data. This costs memory and CPU — you're duplicating the entire String.

Option 2: Borrow instead of taking ownership

fn main() {
    let s1 = String::from("hello");
    
    let len = calculate_length(&s1);  // borrow s1, don't take ownership
    
    println!("The length of '{}' is {}", s1, len);  // s1 is still valid!
}

fn calculate_length(s: &String) -> usize {
    s.len()
}  // s goes out of scope, but it didn't own the String, so nothing is dropped

We'll cover borrowing in detail in the next chapter. For now, just know that & means "reference" — you're letting a function use a value without taking ownership of it.

The Dropping Rule

When a variable goes out of scope, Rust automatically calls the drop function to clean up the memory. This happens at the end of every scope:

fn main() {
    {                      // s1 is not valid here, not declared yet
        let s1 = String::from("hello");
        // s1 is valid from this point forward
    }                      // s1 goes out of scope here, memory is freed
}

You don't need to manually free memory. You don't need a garbage collector. The compiler inserts the drop calls for you, and they're guaranteed to run exactly once.

Why Ownership Matters

At this point, you might be thinking: "This seems complicated. Why would I want this?"

Because ownership eliminates:

And here's the thing: once ownership becomes intuitive, it stops feeling like a restriction. It starts feeling like relief. You write code, the compiler verifies it's correct, and you deploy with confidence.

The Mental Shift

The hardest part of ownership isn't learning the rules — it's unlearning the patterns from other languages. In Python, you pass objects to functions all the time without thinking about what happens to the original. In JavaScript, you mutate objects freely and let the GC clean up.

In Rust, every value has a clear lifecycle. Every assignment potentially moves ownership. Every function call potentially transfers ownership. The compiler tracks all of this, and if you get it wrong, it tells you — before you ship.

This is the foundation everything else builds on. Borrowing, lifetimes, traits — they all exist to help you express ownership patterns that would be impossible or dangerous in other languages.


Next: Chapter 3 — Borrowing