|
|
@ -6,26 +6,28 @@ Safe Rust guarantees an absence of data races, which are defined as:
|
|
|
|
* one or more of them is a write
|
|
|
|
* one or more of them is a write
|
|
|
|
* one or more of them is unsynchronized
|
|
|
|
* one or more of them is unsynchronized
|
|
|
|
|
|
|
|
|
|
|
|
A data race has Undefined Behavior, and is therefore impossible to perform
|
|
|
|
A data race has Undefined Behavior, and is therefore impossible to perform in
|
|
|
|
in Safe Rust. Data races are *mostly* prevented through Rust's ownership system:
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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.**
|
|
|
|
**However Rust does not prevent general race conditions.**
|
|
|
|
|
|
|
|
|
|
|
|
This is pretty fundamentally impossible, and probably honestly undesirable. Your
|
|
|
|
This is mathematically impossible in situations where you do not control the
|
|
|
|
hardware is racy, your OS is racy, the other programs on your computer are racy,
|
|
|
|
scheduler, which is true for the normal OS environment. If you do control
|
|
|
|
and the world this all runs in is racy. Any system that could genuinely claim to
|
|
|
|
preemption, it _can be_ possible to prevent general races - this technique is
|
|
|
|
prevent *all* race conditions would be pretty awful to use, if not just
|
|
|
|
used by frameworks such as [RTIC](https://github.com/rtic-rs/rtic). However,
|
|
|
|
incorrect.
|
|
|
|
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
|
|
|
|
For this reason, it is considered "safe" for Rust to get deadlocked or do
|
|
|
|
something nonsensical with incorrect synchronization. Obviously such a program
|
|
|
|
something nonsensical with incorrect synchronization: this is known as a general
|
|
|
|
isn't very good, but Rust can only hold your hand so far. Still, a race
|
|
|
|
race condition or resource race. Obviously such a program isn't very good, but
|
|
|
|
condition can't violate memory safety in a Rust program on its own. Only in
|
|
|
|
Rust of course cannot prevent all logic errors.
|
|
|
|
conjunction with some other unsafe code can a race condition actually violate
|
|
|
|
|
|
|
|
memory safety. For instance:
|
|
|
|
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
|
|
|
|
```rust,no_run
|
|
|
|
use std::thread;
|
|
|
|
use std::thread;
|
|
|
@ -58,6 +60,9 @@ thread::spawn(move || {
|
|
|
|
println!("{}", data[idx.load(Ordering::SeqCst)]);
|
|
|
|
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
|
|
|
|
```rust,no_run
|
|
|
|
use std::thread;
|
|
|
|
use std::thread;
|
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|