One of the main concurrency problem that programmers face is the Data Race problem. Basically a Data Race occurs when two threads access the same mutable object and while one of the thread is accessing the object the other thread is writting on it.

Let’s take this python code to examplify an data race problem:

import threading

# Shared counter variable
counter = 0

# Function to increment the counter
def increment_counter():
    global counter
    for _ in range(1000000):
        counter += 1

# Create two threads that concurrently increment the counter
thread1 = threading.Thread(target=increment_counter)
thread2 = threading.Thread(target=increment_counter)

# Start both threads
thread1.start()
thread2.start() 

# Wait for both threads to finish
thread1.join()
thread2.join()

# Print the final value of the counter
print("Final Counter Value:", counter)

In this example, two threads (thread1 and thread2) are created to increment the counter variable concurrently. They both run the increment_counter function, which loops 1,000,000 times, incrementing the counter by 1 in each iteration. As a result, the final value of the counter is unpredictable, and it will almost certainly be less than 2,000,000.

Data Race prevention in Rust

Unlike the most multi-threaded languages, Rust make it significantly harder to write code that results in data races, thanks to the Ownership and Borrowing functionalities. The rust version of the above Python code give us an compiler error:

fn main() {
    // Shared counter variable
    let mut counter = 0;

    // Function to increment the counter
    fn increment_counter(counter: &mut i32) {
        for _ in 0..1_000_000 {
            *counter += 1;
        }
    }

    // Create two threads that concurrently increment the counter
    let thread1 = std::thread::spawn(|| {
        increment_counter(&mut counter);
    });

    let thread2 = std::thread::spawn(|| {
        increment_counter(&mut counter);
    });

    // Wait for both threads to finish
    thread1.join().unwrap();
    thread2.join().unwrap();

    // Print the final value of the counter
    println!("Final Counter Value: {}", counter);
}
error[E0502]: cannot borrow `counter` as mutable because it is also borrowed as immutable
  --> main.rs:13:5
   |
11 |     let thread1 = std::thread::spawn(|| {
   |                  ------------------ immutable borrow occurs here
12 |         increment_counter(&mut counter);
13 |     });
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
14 | 
15 |     let thread2 = std::thread::spawn(|| {
   |                  ------------------ immutable borrow ends here

As we can see, Rust don’t let we use the same mutable variable twice in the same scope, which prevent Data Races problems in compiler time.