Reading — step 1 of 6
Learn
Rust's concurrency story is built on the borrow checker — most data races become compile errors. The std::thread module provides the basics; Send and Sync traits gate which types can cross thread boundaries.
Spawning threads
use std::thread;
let handle = thread::spawn(|| {
for i in 1..=5 {
println!("thread: {}", i);
}
});
for i in 1..=5 {
println!("main: {}", i);
}
handle.join().unwrap(); // wait for the thread to finish
thread::spawn returns a JoinHandle. Call .join() to wait for it. The closure runs concurrently with the main thread.
Moving data into threads
By default, threads can't borrow from the spawning scope:
let data = vec![1, 2, 3];
// thread::spawn(|| { println!("{:?}", data); }); // ✗ might outlive data
thread::spawn(move || {
println!("{:?}", data); // ✓ moved into thread
}).join().unwrap();
move forces the closure to take ownership. Required for cross-thread closures since the spawner can't track lifetimes.
Send and Sync — automatic traits
Send— safe to TRANSFER ownership across threads. Most types are Send (anything that doesn't have OS-thread-specific state).Sync— safe to SHARE references across threads (i.e.,&Tis Send).
Both are auto-derived. The compiler checks them silently.
use std::rc::Rc;
use std::sync::Arc;
let rc = Rc::new(42);
// thread::spawn(move || { println!("{}", rc); }); // ✗ Rc isn't Send
let arc = Arc::new(42);
thread::spawn(move || { println!("{}", arc); }); // ✓ Arc is Send
The compiler refuses to compile code that violates Send/Sync — eliminates whole classes of data races.
Shared state via Mutex
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut c = counter.lock().unwrap();
*c += 1;
}));
}
for h in handles { h.join().unwrap(); }
println!("{}", counter.lock().unwrap()); // 10
Arc<Mutex<T>> is the canonical "shared mutable state across threads" pattern. lock() returns a MutexGuard that auto-unlocks when dropped (RAII).
Channels
For message-passing concurrency:
use std::sync::mpsc;
use std::thread;
let (tx, rx) = mpsc::channel();
for i in 0..5 {
let tx = tx.clone();
thread::spawn(move || {
tx.send(i * i).unwrap();
});
}
drop(tx); // close the original sender
for v in rx {
println!("got: {}", v);
}
Receiver iteration ends when all senders are dropped.
Common mistakes
- Forgetting
movewhen the closure needs to outlive the spawner — compile error guides you. - Sharing
Rc<T>across threads — won't compile. UseArc. - Holding a Mutex during a long operation — blocks other threads. Lock briefly.
- Spawning many threads without bounding — use a thread pool (
rayon,tokio) for production work.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…