Every pointer type
&T, &mut T, slices, Box, Rc, Arc, Weak, Cell and RefCell, and raw pointers. What each one owns, who can change the value, and what it costs.
Go down this list and stop at the first one that fits.
- Borrowing for a while →
&Tto read,&mut Tto change. Zero cost, checked at compile time. Borrowing part of a sequence → a slice,&[T]or&str. - One owner, but the value must be on the heap (a recursive type,
a large value you move around, a
dyn Trait) →Box<T>. - Several owners, one thread →
Rc<T>. Several threads →Arc<T>. A back-pointer that mustn't keep the value alive (a child pointing at its parent) →Weak<T>. - Shared, but it still has to change → put
Cell<T>(smallCopyvalues) orRefCell<T>inside anRc;Mutex<T>orRwLock<T>inside anArc. - Talking to C, or building a data structure the borrow checker
can't follow → a raw pointer,
*const T/*mut T, insideunsafe, wrapped in a safe API.
The further down you go, the more you pay at run time or the more you have to check yourself.
Borrowed
Section
&T
A shared, read-only borrow.
Section
&mut T
An exclusive borrow that may change the value.
Section
Slices, &[T] and &str
A borrow of part of a sequence, carrying its length.
Term
fat pointer
Example
size_of::<&str>() is 16: an address and a length. A &dyn Display is also 16: an address and a vtable pointer.Owning
Section
Box
One owner, the value on the heap. For recursive types and trait objects.
Section
Deref and Drop
The two traits that make a type behave like a pointer and clean up after itself.
Section
Rc
Counted shared ownership on one thread.
Example
let a = Arc::new(cfg); let b = Arc::clone(&a); makes Arc::strong_count(&a) 2; the config is freed when both are dropped.Rc's count, and Arc's, which is updated atomically so threads can share it.
Example
Weak<Node> to its parent; parent.upgrade() gives Some(Rc<Node>) while the parent is still alive.Changing a shared value
Section
Cell and RefCell
Interior mutability on one thread.
Formal. RefCell<T> allows mutation through a shared reference by
tracking borrows at run time: borrow() hands out any number of shared
guards and borrow_mut() one exclusive guard, and each guard releases
its borrow when dropped. Asking for a borrow that would break the rules
(an exclusive one while any other exists, or a shared one while an
exclusive one exists) panics ("RefCell already borrowed");
try_borrow/try_borrow_mut return an error instead. RefCell is not
Sync, so it is single-threaded only.
In plain English. The same rule as always, many readers or one writer, but checked while the program runs instead of when it compiles. Break it and the program stops with a panic instead of a compile error.
Example. let log = RefCell::new(Vec::new()); let a = log.borrow(); log.borrow_mut().push(1); panics, because a is still reading.
Why this rule exists. Some designs (a graph, a test fake that records
calls through &self) need shared data that changes; RefCell keeps them
memory-safe when the compiler can't prove the borrows statically.
The borrowing rules, checked when the program runs; break them and it panics.
Section
RwLock
Many readers or one writer, across threads.
Unchecked
Section
Raw pointers
*const T and *mut T: no lifetime, no borrow check, and dereferencing needs unsafe.
Section
Pointers across FFI
Box::into_raw hands ownership to C; Box::from_raw takes it back.
Choosing
Section
Choosing a pointer
The decision above, worked through on real cases.
Section
What each allocation costs
When a Box or a clone shows up in a profile, and what to do about it.