Skip to main content

Module thread_safety

Module thread_safety 

Source
Expand description

§Thread Safety Primitives

Based on “Formal methods for the unsafe side of the Force” (Antithesis, 2026). Provides rigorously defined primitives for bridging FFI and multi-threaded boundaries.

§RelaxedAtomic<T>

Provides inner mutability for Copy types via relaxed atomic loads and stores. On x86_64 and ARM, relaxed loads/stores compile to the same instructions as regular memory accesses (no LOCK prefix), making this a zero-overhead way to achieve interior mutability for atomic-compatible types.

For u32, provides fetch_add and fetch_sub methods that use atomic read-modify-write operations. These are atomic but emit LOCK-prefixed instructions on x86_64 (though without the stronger ordering fence overhead of SeqCst).

For simple load-mutate-store patterns, use the loadstore methods:

let counter = RelaxedAtomic::new(0u32);
let val = counter.load();
counter.store(val + 1);

For atomic increments/decrements, use fetch_add/fetch_sub:

let counter = RelaxedAtomic::new(0u32);
counter.fetch_add(1); // Atomic, no race condition

§WARNING: Race Conditions Are Still Possible

Rust prevents data races, not race conditions. (See “Rust Prevents Data Races, Not Race Conditions” by Matthias Endler.)

A data race is unsynchronized concurrent access where at least one side writes. This is Undefined Behavior and Rust’s type system prevents it.

A race condition is any bug where the result depends on timing or thread interleaving. Rust does not prevent these.

The load–mutate–store pattern is not atomic as a whole:

// DANGEROUS: Two threads can interleave between load and store
let val = counter.load();
// <--- Another thread could load and store here
counter.store(val + 1);

This is the classic TOCTOU (Time-of-Check-Time-of-Use) bug. See the bank account example in the article above.

§When to use

Use when a field needs interior mutability and is accessed without contention (same pattern as the original C code using plain loads/stores). If you need multi-step atomic operations (CAS, fetch_add), use the underlying std::sync::atomic types directly.

§When not to use

Do not use when the operation must be atomic relative to other threads. The load–mutate–store pattern is not atomic as a whole — it can race with concurrent stores. Use only where the C code would have used a non-atomic access that happens to be race-free by design.

§Correct usage examples

// CORRECT: Single-threaded or single-writer scenario
let flag = RelaxedAtomic::new(false);
// Only one thread ever writes to this
flag.store(true);

// CORRECT: Using fetch_add for atomic increment
let counter = RelaxedAtomic::new(0u32);
counter.fetch_add(1); // Atomic, no race condition

// CORRECT: Read-only scenario
let config = RelaxedAtomic::new(42u32);
let val = config.load(); // Multiple readers, no writers

§Incorrect usage examples

// INCORRECT: Non-atomic compound operation
let counter = RelaxedAtomic::new(0u32);
// Two threads doing this simultaneously can lose updates
let val = counter.load();
counter.store(val + 1);

// INCORRECT: Check-then-act (TOCTOU)
let balance = RelaxedAtomic::new(100u32);
// Thread A: check balance
let can_withdraw = balance.load() >= 100;
// <--- Thread B could withdraw here
// Thread A: withdraw
if can_withdraw {
    balance.store(balance.load() - 100);
}

Structs§

MainThreadToken
A witness of execution that exists solely on a designated “Main Thread”.
RelaxedAtomic
Provides inner mutability for Copy types via relaxed atomic operations.
SendWrapper
A wrapper that allows sending non-Send types across thread boundaries.

Traits§

AtomicRepr
Trait for types that can be stored in a RelaxedAtomic.

Functions§

designate_main_thread
Designate the calling thread as the application’s main thread.
main_thread_id
Returns the ThreadId previously designated as the main thread, if any.