Smart pointers
Until now one value had one owner, or a borrow. This lesson adds three owners that are still explicit. The thread lesson is where those handles are actually spawned. Learn the pointer first so the spawn has somewhere legal to send it.
| Pointer | Owners | Threads |
|---|---|---|
Box | One | The owner moves with the box |
Rc | Many, one allocation | One thread. Rc is not Send. |
Arc | Many, one allocation | Safe to send when the inner type is Send and Sync |
Mutex | The lock, not a second owner | Only one thread holds the guard. It unlocks in Drop. |
Box
Box owns a T on the heap. There is still one owner. Drop frees it. You need it when a type contains itself, because otherwise the size would be infinite: enum List { Cons(i32, Box<List>), Nil }. You also use it to move a large value by moving a pointer, and to hold a trait object, Box<dyn Trait>, which you already saw as a fat pointer.
Moving the Box moves one pointer. Deref lets you call T’s methods on the box. *boxed moves or copies the inner value out when the type allows it.
enum List {
Cons(i32, Box<List>),
Nil,
}
fn main() {
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
if let List::Cons(head, rest) = list {
println!("{head}");
drop(rest);
}
}Rc and RefCell
Rc is shared ownership on one thread. Cloning an Rc increments a strong count. It does not clone the inner value. When the last Rc drops, the value drops. Weak is a non-owning pointer so two values can point at each other without a cycle that never frees.
Rc::clone only copies the pointer and bumps the count. This is still one thread. Rc is not Send, so you cannot move it to another thread.
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let shared = Rc::new(RefCell::new(vec![1, 2]));
let alias = Rc::clone(&shared);
alias.borrow_mut().push(3);
println!("{} {}", shared.borrow().len(), Rc::strong_count(&shared));
}Arc and Mutex
Arc is the same shared owner, safe to send to another thread when the inner type is Send and Sync. A Mutex wraps the value so only one thread holds the lock. lock returns a guard. The guard unlocks in Drop, the same RAII you used for files. If a thread panics while holding the guard, the lock is poisoned. The next lock().unwrap() surfaces that. Prefer not to hold the guard longer than the update.
Each thread would get its own Arc clone pointing at one mutex. Sending happens in the next lesson. The type has to be legal before you spawn.
use std::sync::{Arc, Mutex};
fn main() {
let counter = Arc::new(Mutex::new(0));
{
let mut guard = counter.lock().unwrap();
*guard += 1;
}
println!("{}", *counter.lock().unwrap());
}