Concurrency & Threads
Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — that last phrase means its type system stops data races at compile time, so multithreaded code is safe by construction.
Learn Concurrency & Threads in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
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, return values from them, pass messages with channels, and share state safely with Arc and Mutex .
What You'll Learn in This Lesson
1️⃣ Spawning Threads and Joining
thread::spawn runs a closure on a new thread and returns a JoinHandle . Call .join() to wait for it to finish and collect its return value. When a thread needs to use outside data, the closure must take ownership of it with move , since the thread may outlive the current scope.
The second thread used move to take ownership of data — that's how the compiler guarantees the vector outlives the thread that reads it.
2️⃣ Channels and Shared State
There are two main ways threads coordinate. Channels ( mpsc ) pass messages: a sender hands values to a receiver. For shared mutable state , wrap the data in an Arc<Mutex<T>> — Arc shares ownership across threads, and Mutex ensures only one thread accesses it at a time.
Five threads each incremented the same counter, and the result was reliably 5 — no data race — because the Mutex serialized access. Try writing that safely in another language and you'll appreciate "fearless".
Your turn. Fill in the blanks marked ___ , then run it.
Use Arc<Mutex<i32>> and four threads to accumulate a shared total. Run it with cargo run .
📋 Quick Reference — Concurrency
Practice quiz
What does thread::spawn return?
- The thread's result directly
- Nothing
- A JoinHandle you can wait on
- A Mutex
Answer: A JoinHandle you can wait on. spawn returns a JoinHandle; calling .join() waits for the thread and yields its result.
What does handle.join() do?
- Waits for the thread to finish and returns its result (a Result)
- Starts a new thread
- Kills the thread immediately
- Locks shared data
Answer: Waits for the thread to finish and returns its result (a Result). join blocks until the thread completes and returns a Result containing its value.
Why do thread closures usually need the move keyword?
- For speed
- To avoid imports
- Because closures cannot borrow at all
- Because the thread may outlive the current scope, so it must own the data it uses
Answer: Because the thread may outlive the current scope, so it must own the data it uses. move makes the closure own its captures so the data lives as long as the thread.
What does 'fearless concurrency' refer to in Rust?
- Threads never crash
- The type system prevents data races at compile time
- Threads are always faster
- There is no need to join
Answer: The type system prevents data races at compile time. Ownership and borrowing rules turn would-be data races into compile errors.
What is the difference between Rc and Arc?
- Rc is single-threaded; Arc is thread-safe (atomic) for sharing across threads
- No difference
- Arc is single-threaded; Rc is for threads
- Arc cannot be cloned
Answer: Rc is single-threaded; Arc is thread-safe (atomic) for sharing across threads. Arc is the atomically reference-counted, thread-safe equivalent of Rc.
Why wrap shared data in a Mutex when sharing across threads?
- To clone it faster
- To sort it
- To ensure only one thread accesses the data at a time
- To avoid using Arc
Answer: To ensure only one thread accesses the data at a time. A Mutex serializes access so only the lock holder can read or write the data.
What is the idiomatic pattern for shared MUTABLE state across threads?
- Rc<RefCell<T>>
- Arc<Mutex<T>>
- Box<T>
- Vec<T>
Answer: Arc<Mutex<T>>. Arc shares ownership across threads and Mutex coordinates safe mutation.
What does mpsc::channel() give you?
- A single value
- Two threads
- A locked Mutex
- A transmitter (tx) and a receiver (rx) for passing messages
Answer: A transmitter (tx) and a receiver (rx) for passing messages. It returns a (tx, rx) pair; senders push values to tx and a receiver reads from rx.
When does iterating over a channel receiver (for x in rx) stop?
- After one message
- When all senders are dropped
- After 60 seconds
- Never
Answer: When all senders are dropped. Receiving iterates until every sender has been dropped, then the loop ends.
When is a Mutex lock released?
- Never automatically
- Only by calling unlock()
- When the guard returned by .lock() is dropped (goes out of scope)
- When the program exits
Answer: When the guard returned by .lock() is dropped (goes out of scope). The lock is held by the guard and released automatically when that guard is dropped.