You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Rust makes concurrent programming safer through its ownership and type system. This lesson covers threads, message passing, shared state, async/await, and the broader Rust ecosystem.
Rust provides OS-level threads through the standard library:
use std::thread;
use std::time::Duration;
let handle = thread::spawn(|| {
for i in 1..5 {
println!("spawned thread: {i}");
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..3 {
println!("main thread: {i}");
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap(); // wait for the spawned thread to finish
Use move to transfer ownership of captured variables:
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("data: {data:?}");
});
// data is no longer accessible here — it was moved
handle.join().unwrap();
Rust uses two marker traits to enforce thread safety at compile time:
| Trait | Meaning |
|---|---|
| Send | A type can be transferred to another thread |
| Sync | A type can be shared between threads via references |
Most types are Send and Sync automatically. Notable exceptions:
| Type | Send | Sync | Why |
|---|---|---|---|
Rc<T> | No | No | Non-atomic reference counting |
RefCell<T> | Yes | No | Runtime borrow checking is not thread-safe |
Arc<T> | Yes | Yes | Atomic reference counting |
Mutex<T> | Yes | Yes | Locked access is thread-safe |
| Raw pointers | No | No | No safety guarantees |
Tip: The compiler will prevent you from sending non-Send types across threads or sharing non-Sync types. You do not need to remember the rules — the compiler enforces them.
Channels allow threads to communicate by sending messages:
use std::sync::mpsc; // multiple producer, single consumer
use std::thread;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let messages = vec!["hello", "from", "the", "thread"];
for msg in messages {
tx.send(msg).unwrap();
}
});
for received in rx {
println!("Got: {received}");
}
Clone the transmitter for multiple producers:
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || { tx.send("from thread 1").unwrap(); });
thread::spawn(move || { tx2.send("from thread 2").unwrap(); });
for received in rx {
println!("Got: {received}");
}
Mutex<T> provides mutual exclusion for shared data:
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);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap()); // 10
RwLock<T> allows multiple readers or one writer:
use std::sync::RwLock;
let lock = RwLock::new(5);
// Multiple readers
{
let r1 = lock.read().unwrap();
let r2 = lock.read().unwrap();
println!("{r1}, {r2}");
}
// Single writer
{
let mut w = lock.write().unwrap();
*w += 1;
}
| Model | When to Use | Mechanism |
|---|---|---|
| Message passing | Independent tasks communicating | mpsc::channel |
| Shared state | Multiple threads need the same data | Arc<Mutex<T>> |
| Read-heavy workloads | Many readers, few writers | Arc<RwLock<T>> |
For I/O-bound concurrency, Rust provides async/await:
async fn fetch_data(url: &str) -> String {
// async implementation
format!("data from {url}")
}
async fn process() {
let data = fetch_data("https://example.com").await;
println!("{data}");
}
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.