Thread Safety Traits: The Send & Sync Formalisms

Master the mathematical relationship between Send and Sync, auto-trait derivation rules, non-thread-safe types, and compile-time data race prevention.

05. Concurrency, Atomics & Asynchronous Architecture7 min read

In C and C++, thread safety is an undocumented convention: developers must remember which objects can be passed across threads and which pointers must be guarded by locks. A single mistake triggers silent data races.

In Rust, thread safety is formally proven at compile time through two built-in marker traits: Send and Sync.

The Equivalence InvariantFormal Theorem
T: Sync <=> &T: Send

A type T is thread-safe to share if and only if an immutable reference to T can be safely sent across threads.

1. Defining Send and Sync#

Unlike normal traits that define methods, Send and Sync are marker traits: they have zero methods and exist purely to convey capabilities to the compiler.

Marker TraitCapability ConferredCore Question Answered
SendSafe to transfer ownership across a thread boundary.Can I move this value into another thread?
SyncSafe to share references (&T) across multiple threads simultaneously.Can multiple threads read this value concurrently?
Architecture Diagram
Rendering diagram...

The Equivalence Theorem#

The relationship between Send and Sync is formalized by a fundamental equivalence:

T:Sync  ⟺  &T:SendT : \text{Sync} \iff \&T : \text{Send}

A type TT is Sync if and only if a shared immutable reference to TT (&T\&T) implements Send.

2. Auto-Trait Derivation Rules#

Send and Sync are auto traits: the compiler automatically implements them for your custom structs and enums if all of their constituent fields implement them:

For a compound struct CC containing fields F1,F2,…,FkF_1, F_2, \dots, F_k:

C:Send  ⟺  ∀i∈{1,…,k},Fi:SendC : \text{Send} \iff \forall i \in \{1, \dots, k\}, \quad F_i : \text{Send}
C:Sync  ⟺  ∀i∈{1,…,k},Fi:SyncC : \text{Sync} \iff \forall i \in \{1, \dots, k\}, \quad F_i : \text{Sync}

If a struct contains even a single field that is not thread safe (such as an Rc pointer or raw pointer), the entire struct automatically loses Send or Sync.

3. The Anatomy of Non-Thread-Safe Types#

Understanding why certain types are marked !Send or !Sync illuminates how Rust eliminates concurrency bugs:

Architecture Diagram
Rendering diagram...

Why Rc<T> is !Send and !Sync#

Rc<T> uses standard, non-atomic CPU instructions (count += 1) to increment its reference count. If two threads cloned an Rc concurrently, a CPU data race would corrupt the counter, causing premature memory deallocation and use-after-free bugs!

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::rc::Rc; use std::thread; fn main() { let rc_value = Rc::new(42); // COMPILE ERROR: `Rc<i32>` cannot be sent between threads safely // thread::spawn(move || { // println!("{}", rc_value); // }); // SOLUTION: Use Arc<T> instead of Rc<T>! }

4. Concept Verification#

Test your understanding of Send and Sync invariants:

Concept check

What is the formal definition of the Sync trait in relation to Send?

Concept check

Why does containing a raw pointer *const u8 make a struct automatically !Send and !Sync?