Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Let's Get Racy

Rust makes data races not compile.

For example:

#include <thread>
#include <vector>
#include <atomic>
#include <iostream>

int main() {
  int counter = 0;
  std::vector<std::thread> handles;
    
  for (int i=0; i<3; i++) {
    handles.push_back(
      std::thread([&counter]() { 
        for (int i=0; i<100000; i++) {
          counter++;
        }
      })
    );
  }

  for (int i=0; i<handles.size(); i++) {
    handles[i].join();
  }

  std::cout << "Counter: " << counter << "\n";
  return 0;
}

This gives you a different answer every time - there's a race condition with multiple threads updating counter all at once.

Rust won't even let you compile this:

fn main() {
    let mut counter = 0;
    let mut handles = Vec::new();
    for _ in 0..3 {
        handles.push(std::thread::spawn(|| {
            for _ in 0..100_000 {
                counter += 1;
            }
        }))
    }
    for handle in handles {
      handle.join();
    }
}

It will fail to compile. The actual compilation error is comically large:

   Compiling playground v0.0.1 (/playground)
error[E0373]: closure may outlive the current function, but it borrows `counter`, which is owned by the current function
 --> src/main.rs:5:41
  |
5 |         handles.push(std::thread::spawn(|| {
  |                                         ^^ may outlive borrowed value `counter`
6 |             for _ in 0..100_000 {
7 |                 counter += 1;
  |                 ------- `counter` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:5:22
  |
5 |           handles.push(std::thread::spawn(|| {
  |  ______________________^
6 | |             for _ in 0..100_000 {
7 | |                 counter += 1;
8 | |             }
9 | |         }))
  | |__________^
help: to force the closure to take ownership of `counter` (and any other referenced variables), use the `move` keyword
  |
5 |         handles.push(std::thread::spawn(move || {
  |                                         ++++

error[E0499]: cannot borrow `counter` as mutable more than once at a time
   --> src/main.rs:5:41
    |
  5 |           handles.push(std::thread::spawn(|| {
    |                        -                  ^^ `counter` was mutably borrowed here in the previous iteration of the loop
    |  ______________________|
    | |
  6 | |             for _ in 0..100_000 {
  7 | |                 counter += 1;
    | |                 ------- borrows occur due to use of `counter` in closure
  8 | |             }
  9 | |         }))
    | |__________- argument requires that `counter` is borrowed for `'static`
    |
note: requirement that the value outlives `'static` introduced here
   --> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:728:15
    |
728 |     F: Send + 'static,
    |               ^^^^^^^

Some errors have detailed explanations: E0373, E0499.
For more information about an error, try `rustc --explain E0373`.
error: could not compile `playground` (bin "playground") due to 2 previous errors

Rust really doesn't want you to make this mistake! (You can solve this particular problem with an AtomicUsize, or use a proper Mutex)