Send, Sync & Shared-State Concurrency
Rust's "fearless concurrency" rests on two marker traits — Send and Sync — that let the compiler reject data races before your program even runs.
Learn Send, Sync & Shared-State Concurrency in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll spawn threads with move closures, share mutable state safely with Arc and Mutex, see why Rc and RefCell stay single-threaded, and learn to dodge deadlocks.
What You'll Learn in This Lesson
1️⃣ Spawning Threads with move
thread::spawn runs a closure on a new OS thread, and the data you capture must be Send . The move keyword transfers ownership of captures into the thread so they outlive the spawning scope.
Because Vec<i32> is Send , the compiler allows moving it across the thread boundary, and join returns the thread's result.
2️⃣ Shared Mutable State with Arc<Mutex<T>>
To mutate one value from many threads, combine two tools: Arc gives atomically reference-counted shared ownership, and Mutex gives synchronized interior mutability. Locking returns a guard that releases automatically when dropped.
For read-heavy data, swap Mutex for RwLock , which lets many readers proceed concurrently while still serializing writers.
3️⃣ Why Rc and RefCell Stay Single-Threaded
Send and Sync are auto traits : the compiler grants them only when every part of a type is safe. Rc uses a non-atomic count and RefCell non-atomic borrow flags, so neither qualifies — using one across threads is a compile error.
The compiler catching this at build time is exactly what "fearless concurrency" means: the unsafe sharing never reaches runtime.
Use Arc<Mutex<T>> to total a value across three threads.
📋 Quick Reference — Concurrency
Practice quiz
What does the Send marker trait indicate about a type?
- Ownership of it can be transferred to another thread
- It is immutable
- It is heap allocated
- It can be cloned
Answer: Ownership of it can be transferred to another thread. A type is Send if it is safe to move its ownership to a different thread.
What does the Sync marker trait indicate about a type T?
- T is copyable
- T uses no heap
- &T can be shared across threads safely
- T is serializable
Answer: &T can be shared across threads safely. T is Sync when &T is Send, meaning a shared reference can be accessed from multiple threads safely.
How are Send and Sync normally implemented?
- Manually for every type
- Automatically (auto traits) when all fields qualify
- By a derive macro you write
- By calling .sync()
Answer: Automatically (auto traits) when all fields qualify. Send and Sync are auto traits: the compiler implements them automatically when all of a type's parts already are.
Why is Rc<T> NOT Send or Sync?
- It lives on the stack
- It cannot be cloned
- It is too large
- Its reference count is non-atomic, so concurrent updates would race
Answer: Its reference count is non-atomic, so concurrent updates would race. Rc uses a non-atomic reference count, so sharing it across threads could corrupt the count; that's why it isn't Send or Sync.
Which smart pointer gives thread-safe shared ownership?
- Arc<T>
- Box<T>
- Rc<T>
- Cell<T>
Answer: Arc<T>. Arc<T> is the atomically reference-counted pointer, safe to clone and share across threads.
To share MUTABLE state across threads, you typically wrap it in...
- Cell<T>
- Arc<Mutex<T>>
- Rc<RefCell<T>>
- Box<T>
Answer: Arc<Mutex<T>>. Arc gives shared ownership across threads and Mutex provides synchronized interior mutability.
What does a thread::spawn closure usually need the move keyword for?
- To inline it
- To make it async
- To copy the stack
- To take ownership of captured variables into the new thread
Answer: To take ownership of captured variables into the new thread. move forces the closure to own its captures so they can safely outlive the spawning scope on the new thread.
When is RwLock often preferred over Mutex?
- When data is tiny
- When you need a non-atomic count
- When there are many readers and few writers
- When there is no contention ever
Answer: When there are many readers and few writers. RwLock allows many concurrent readers OR one writer, which helps read-heavy workloads.
What causes a classic deadlock with two mutexes?
- Reading without a lock
- Two threads each holding one lock and waiting for the other
- Using a channel
- Cloning an Arc
Answer: Two threads each holding one lock and waiting for the other. If threads acquire two locks in opposite orders, each can wait forever for the other; consistent lock ordering avoids it.
What is the idiomatic alternative to sharing mutable state directly?
- Message passing over channels
- Disabling the borrow checker
- Global mutable statics
- Spinning in a loop
Answer: Message passing over channels. Rust encourages 'do not communicate by sharing memory; share memory by communicating' using channels like mpsc.