August 2, 2025•5 min

How does ownership prevent memory leaks and data races?

m
mayo

Ownership is Rust's core memory management system, enforcing strict rules at compile time to ensure safety without a garbage collector. It prevents memory leaks and data races through a combination of ownership rules, move semantics, and borrowing.

s1 (moved) invalid: use after move s2 new owner of the String "hello" heap buffer (unchanged) move take_ownership(s) → s dropped when function scope ends only one owner exists at a time: no double free, no leak

Ownership in Rust

  • Each value has a single owner (variable).
  • When the owner goes out of scope, the value is dropped (Drop trait called).
  • Ownership can be transferred (moved), making the original variable invalid.

Key Rules

Move Semantics

Assigning a heap-allocated value (e.g., String) to another variable transfers ownership, invalidating the original.

Example:

let s1 = String::from("hello");
let s2 = s1; // Ownership moved to s2
// println!("{}", s1); // Compile error: value borrowed after move

Copy vs. Move

  • Types with known size (i32, bool) implement Copy and are cloned automatically.
  • Heap-allocated types (String, Vec) do not implement Copy and are moved.

Function Calls

Passing a value to a function moves or copies it, following the same rules.

Example:

fn take_ownership(s: String) { /* ... */ }

let s = String::from("hello");
take_ownership(s); // Ownership moved into the function
// println!("{}", s); // Error: s is invalid

How Ownership Prevents Memory Leaks

  • Automatic Cleanup: When the owner goes out of scope, Rust calls drop to free memory (no manual free() needed).
  • No Double Frees: Since only one owner exists, the value is dropped exactly once.

How Ownership Prevents Data Races

  • Borrowing Rules:
    • Immutable borrows (&T): Multiple allowed, but no mutable borrows can coexist.
    • Mutable borrows (&mut T): Only one allowed, and no other borrows can exist.
  • Compile-Time Enforcement: The compiler rejects code that could lead to data races.

Example: Data Race Prevention:

let mut data = vec![1, 2, 3];

let r1 = &data; // Immutable borrow OK
let r2 = &data; // Another immutable borrow OK
// let r3 = &mut data; // ERROR: Cannot borrow as mutable while immutable borrows exist

println!("{:?}, {:?}", r1, r2);
What the borrow checker allows next Borrows already live you ask for &data you ask for &mut data none allowed first shared borrow allowed exclusive access r1 = &data, r2 = &data any number of shared allowed shared borrows stack up rejected cannot borrow as mutable m = &mut data exactly one rejected already mutably borrowed rejected only one &mut at a time In the example r1 and r2 are still live, so `let r3 = &mut data;` lands in the highlighted cell.

Key Takeaways

Ownership ensures:

  • No dangling pointers (via lifetimes).
  • No memory leaks (via Drop).
  • No data races (via borrowing rules).

Rust’s ownership model guarantees memory safety and concurrency safety at compile time, delivering performance and reliability.

  1. Step 9 of 58•6 min
    Mutable vs. immutable borrows: the two rules

    The two borrowing rules, why exclusivity is the one that matters, and what the borrow checker actually rejects.

  2. Step 10 of 58•5 min
    How does Rust prevent dangling pointer at compile time?
  3. Step 11 of 58•8 min
    What is &mut *x (reborrow) in Rust, and why does it freeze the original reference?