Lesson 11 of 12
Concurrency & Smart Pointers
Learn how to run code on multiple threads and share data safely with smart . Rust's ownership rules turn data races into compile .
use std::thread;
use std::sync::mpsc;
use std::time::Duration;
fn main() {
// Threads: move takes ownership of what the closure captures
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("{:?}", data);
data.iter().sum::<i32>()
});
let sum = handle.join().unwrap(); // join returns the closure's value
println!("sum = {sum}");
// Channels: multiple producers, single consumer
let (tx, rx) = mpsc::channel();
for id in 0..3 {
let tx = tx.clone();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50 * id));
tx.send(format!("worker {id} done")).unwrap();
});
}
drop(tx); // drop the last sender so rx can finish
for msg in rx { // iterates until every sender is gone
println!("{msg}");
}
}use std::sync::{Arc, Mutex, RwLock};
use std::thread;
fn main() {
// Arc = Atomically Reference Counted: shared ownership across threads.
// Mutex = mutual exclusion: one writer at a time.
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..8 {
let counter = Arc::clone(&counter); // clones the HANDLE, not the data
handles.push(thread::spawn(move || {
let mut n = counter.lock().unwrap(); // blocks until the lock is free
*n += 1;
})); // lock released here, automatically
}
for h in handles { h.join().unwrap(); }
println!("{}", *counter.lock().unwrap()); // always 8 — no race possible
// RwLock: many readers OR one writer
let cache = Arc::new(RwLock::new(vec!["a".to_string()]));
{
let read = cache.read().unwrap();
println!("{}", read.len());
}
cache.write().unwrap().push("b".into());
// Rc<RefCell<T>> is the single-threaded equivalent (Rc is not thread safe).
}use std::rc::Rc;
use std::cell::RefCell;
// Box<T>: a value on the heap. Needed for recursive types of unknown size.
#[derive(Debug)]
enum Tree {
Leaf(i32),
Node(Box<Tree>, Box<Tree>),
}
fn sum(tree: &Tree) -> i32 {
match tree {
Tree::Leaf(v) => *v,
Tree::Node(l, r) => sum(l) + sum(r),
}
}
// Rc<T>: shared ownership in one thread. RefCell<T>: borrow rules checked at RUNTIME.
#[derive(Debug)]
struct Account { balance: RefCell<f64> }
fn main() {
let tree = Tree::Node(
Box::new(Tree::Leaf(1)),
Box::new(Tree::Node(Box::new(Tree::Leaf(2)), Box::new(Tree::Leaf(3)))),
);
println!("{}", sum(&tree)); // 6
let account = Rc::new(Account { balance: RefCell::new(100.0) });
let a = Rc::clone(&account);
let b = Rc::clone(&account);
*a.balance.borrow_mut() += 50.0; // mutate through a shared reference
*b.balance.borrow_mut() -= 20.0;
println!("{} owners, balance {}", Rc::strong_count(&account), account.balance.borrow());
}Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.