Skip to content

Commit

Permalink
Reword the section on general race conditions
Browse files Browse the repository at this point in the history
The section on preventing general race conditions is a bit hand wavy. Change wording to be more concrete, and add an example of Rust preventing general races in a very specific case.
  • Loading branch information
tgross35 committed Nov 19, 2023
1 parent 1842257 commit 97a628e
Showing 1 changed file with 22 additions and 17 deletions.
39 changes: 22 additions & 17 deletions src/races.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,32 @@

Safe Rust guarantees an absence of data races, which are defined as:

* two or more threads concurrently accessing a location of memory
* one or more of them is a write
* one or more of them is unsynchronized
- two or more threads concurrently accessing a location of memory
- one or more of them is a write
- one or more of them is unsynchronized

A data race has Undefined Behavior, and is therefore impossible to perform
in Safe Rust. Data races are *mostly* prevented through Rust's ownership system:
A data race has Undefined Behavior, and is therefore impossible to perform in
Safe Rust. Data races are _mostly_ prevented through Rust's ownership system:
it's impossible to alias a mutable reference, so it's impossible to perform a
data race. Interior mutability makes this more complicated, which is largely why
we have the Send and Sync traits (see below).
we have the Send and Sync traits (see the next section for more on this).

**However Rust does not prevent general race conditions.**

This is pretty fundamentally impossible, and probably honestly undesirable. Your
hardware is racy, your OS is racy, the other programs on your computer are racy,
and the world this all runs in is racy. Any system that could genuinely claim to
prevent *all* race conditions would be pretty awful to use, if not just
incorrect.
This is mathematically impossible in situations where you do not control the
scheduler, which is true for the normal OS environment. If you do control
preemption, it _can be_ possible to prevent general races - this technique is
used by frameworks such as [RTIC](https://github.com/rtic-rs/rtic). However,
actually having control over scheduling is a very uncommon case.

So it's perfectly "fine" for a Safe Rust program to get deadlocked or do
something nonsensical with incorrect synchronization. Obviously such a program
isn't very good, but Rust can only hold your hand so far. Still, a race
condition can't violate memory safety in a Rust program on its own. Only in
conjunction with some other unsafe code can a race condition actually violate
memory safety. For instance:
For this reason, it is considered "safe" for Rust to get deadlocked or do
something nonsensical with incorrect synchronization: this is known as a general
race condition or resource race. Obviously such a program isn't very good, but
Rust of course cannot prevent all logic errors.

In any case, a race condition cannot violate memory safety in a Rust program on
its own. Only in conjunction with some other unsafe code can a race condition
actually violate memory safety. For instance, a correct program looks like this:

```rust,no_run
use std::thread;
Expand Down Expand Up @@ -58,6 +60,9 @@ thread::spawn(move || {
println!("{}", data[idx.load(Ordering::SeqCst)]);
```

We can cause a data race if we instead do the bound check in advance, and then
unsafely access the data with an unchecked value:

```rust,no_run
use std::thread;
use std::sync::atomic::{AtomicUsize, Ordering};
Expand Down

0 comments on commit 97a628e

Please sign in to comment.