Post

Ownership Simplified: Rust's Central Invariant

Ownership is not a feature of Rust. It IS Rust. Three rules, enforced at compile time, that eliminate use-after-free, double-free, and data races — without a garbage collector.

Ownership Simplified: Rust's Central Invariant

Rust guarantees memory safety with a feature called ownership. But calling it a “feature” undersells it. Ownership is the central invariant of the entire language. Every other safety guarantee — no dangling pointers, no data races, no use-after-free — follows from ownership. Break the ownership rules and the compiler refuses to compile your program. Not at runtime, not in tests — at compile time.

Memory Safety

Memory safety means that every pointer used in a program points to valid memory. Violations include:

  • Dangling pointers — pointers to memory that has been freed
  • Double-free — freeing the same memory twice
  • Use-after-free — reading memory after it has been deallocated
  • Buffer overflows — writing past the end of allocated memory

Languages with garbage collectors (Java, Go, Python) handle this by tracking references at runtime and freeing memory when nothing points to it anymore. This works, but it costs performance and introduces unpredictable pauses.

Languages without garbage collectors (C, C++) leave memory management to the developer. This is fast but error-prone — entire categories of security vulnerabilities exist because developers forgot to free memory, freed it twice, or used it after freeing.

Rust takes a third path: the ownership model. A set of rules, checked at compile time, that make memory violations structurally impossible. No runtime cost. No garbage collector. No discipline required — the compiler does the checking.

The Stack and The Heap

To understand ownership, you need to understand where values live.

Stack

The stack stores values of fixed size. Memory management follows Last In, First Out order. Think of a stack of plates: you add plates on top and remove from the top. Operations on the stack are fast because the size is known and access is direct.

All primitive types (i32, bool, f64, char) and fixed-size types live on the stack.

Heap

The heap stores values whose size is unknown at compile time or may change. When you allocate on the heap, the memory allocator finds space, marks it as used, and returns a pointer.

Heap access is slower than stack access. It also requires bookkeeping: tracking which parts of code use which data, deduplicating, and cleaning up.

String, Vec<T>, and other dynamically-sized types allocate on the heap. Ownership determines when that heap memory is freed.

The Three Rules of Ownership

These are the invariants:

  1. Each value in Rust has exactly one 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. Three rules. From these three rules, Rust derives memory safety without a garbage collector.

Variable Scope

A scope is the range within a program where a variable is valid:

1
2
3
4
5
6
7
8
fn main() {
    let y = 6;
    {
        let x = y + 1; // x comes into scope
        println!("{}", x);
    } // x goes out of scope — memory is freed
    println!("{}", y); // y is still valid
}

When x goes out of scope at the closing brace, Rust automatically calls drop on it, freeing any resources it owns. This is deterministic — you know exactly when memory is freed, unlike garbage-collected languages where it happens “eventually.”

The ownership invariant at work: x owns its value. When x ceases to exist, the value ceases to exist. No dangling pointer can reference it because the compiler tracks the lifetime and prevents any reference from outliving the owner.

Ownership Transfer (Move Semantics)

When you assign a heap-allocated value to another variable, ownership moves:

1
2
3
4
5
6
fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // ownership moves from s1 to s2
    // println!("{}", s1); // ERROR: s1 is no longer valid
    println!("{}", s2);
}

After the move, s1 is no longer valid. Attempting to use it is a compile error. This enforces Rule 2 — there can only be one owner at a time. Two variables cannot both own the same heap memory, because then both would try to free it when they go out of scope (double-free).

This is the invariant that C and C++ developers have to enforce through discipline: “don’t use this pointer after transferring ownership.” Rust makes it a compile-time error.

Ownership and Functions

Passing a value to a function transfers ownership:

1
2
3
4
5
6
7
8
9
fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    // println!("{}", s); // ERROR: s was moved into the function
}

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

The function now owns the value. When the function returns, the value is dropped. The caller can no longer use it. This is not a gotcha — it’s the invariant working correctly. If you want to use the value after the function call, you need borrowing (covered in the next post).

Why This Matters

The ownership model is what makes Rust unique among systems languages. It provides the same performance as manual memory management (no GC pauses, no runtime overhead) with the same safety as garbage-collected languages (no dangling pointers, no leaks). The trade-off is that you have to satisfy the compiler — but the compiler is checking invariants that you’d need to check anyway. It just does it before the code runs.

ProblemC/C++ SolutionGC Language SolutionRust Solution
Dangling pointerDeveloper disciplineRuntime GCCompile-time ownership
Double-freeDeveloper disciplineRuntime GCCompile-time ownership
Memory leakDeveloper disciplineRuntime GCCompile-time drop
Data raceDeveloper disciplineRuntime locksCompile-time ownership + borrowing

Every cell in the Rust column says “compile-time.” That’s the invariant at work.


Ownership is the foundation. But real programs need to share data without transferring ownership. That’s where borrowing and references come in — Rust’s system for temporary, controlled access to values you don’t own. That’s the next post.

This post is licensed under CC BY 4.0 by the author.