Course contentsShow
← Rust
Lesson 1082 of 1,91010. Concurrency PrimitivesFree lesson

The Mental Model of Rust Ownership in Concurrency

You will learn how the borrow checker prevents data races at compile time by controlling shared mutable access.

In this lesson, you will learn how Rust’s ownership rules act as a compile-time firewall against the notorious "data race" condition by strictly enforcing exclusive access to shared state.

The Conflict of Concurrent Access

In languages without Rust's ownership model, data races occur when two threads access the same memory location simultaneously, and at least one of those accesses is a write. This results in undefined behavior—the program state becomes unpredictable because the threads are effectively "fighting" over the data without synchronization.

Ownership as a Traffic Controller

Rust prevents this by applying the exact same rules to threads that it applies to local variables. Recall the core constraint: You can have either many immutable references OR exactly one mutable reference.

When you send a variable to a thread, ownership is moved. If you want to share data, you must provide a reference. If that reference allows mutation, the borrow checker enforces that no other thread can hold a reference to that data at the same time.

Consider this mental model:

  • Immutable sharing (&T): Rust allows multiple threads to hold a read-only view. Because no one can write, no race is possible.
  • Mutable sharing (&mut T): Rust forbids this across threads by default. To mutate safely, you must prove to the compiler that you have exclusive access, usually via a synchronization primitive that grants that "mutable permit" to only one thread at a time.
use std::thread;

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

// This fails to compile! 
// The borrow checker sees that `data` could be modified by the main thread 
// while the spawned thread is reading it.
thread::spawn(|| {
    println!("{:?}", data); 
});

Because the compiler knows the spawned thread might outlive the current scope, it demands that data be moved entirely into the thread or wrapped in a structure that provides safe, serialized access. By forcing these constraints, the compiler turns a potential runtime nightmare into a simple, solvable build error.

Key Takeaway: Rust uses the borrow checker to ensure that concurrent access is either read-only or strictly exclusive, making data races impossible to express in safe code.