Fearless Concurrency in Rust
Rust's ownership system prevents data races at compile time. This means you can write concurrent code with confidence, knowing that entire classes of bugs are impossible.
The standard library provides threads, channels, and synchronization primitives. Let's explore how they work together safely.
use std::thread;
fn main() {
let handle = thread::spawn(|| {
for i in 1..=5 {
println!("Spawned thread: {}", i);
thread::sleep(std::time::Duration::from_millis(1));
}
});
for i in 1..=3 {
println!("Main thread: {}", i);
thread::sleep(std::time::Duration::from_millis(1));
}
handle.join().unwrap();
}
Try it Yourself ->
Move Closures for Thread Ownership
When you spawn a thread, you often need to move data into it. The move keyword transfers ownership of captured variables to the new thread.
use std::thread;
fn main() {
let names = vec!["Alice", "Bob", "Charlie"];
let handle = thread::spawn(move || {
for name in &names {
println!("Hello from {}", name);
}
});
handle.join().unwrap();
// names is moved, can't use it here
}
Try it Yourself ->
Message Passing with Channels
Channels provide a safe way to communicate between threads. Think of them as typed pipes that send data from one thread to another.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let messages = vec!["hello", "from", "the", "other", "side"];
for msg in messages {
tx.send(msg).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
}
});
for received in rx {
println!("Got: {}", received);
}
}
Try it Yourself ->
Mutex<T> for Shared State
Mutex (Mutual Exclusion) ensures only one thread can access data at a time. It's essential for protecting shared state.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
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!("Final count: {}", *counter.lock().unwrap());
}
Try it Yourself ->
Arc<T> for Thread-Safe Reference Counting
Arc (Atomically Reference Counted) is the thread-safe version of Rc. It lets multiple threads own the same data safely.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let mut handles = vec![];
for i in 0..3 {
let data = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut vec = data.lock().unwrap();
vec.push(i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final data: {:?}", *data.lock().unwrap());
}
Try it Yourself ->
Why Rust Prevents Data Races
Rust's ownership system enforces rules that make data races impossible. You can't have mutable and immutable references to the same data at the same time. The compiler catches these issues before your code runs.
This is what "fearless concurrency" means: you can write concurrent code knowing that the compiler has your back. The type system prevents the bugs that plague concurrent programs in other languages.