Step 1 of 4 · Reading · ~3 min
Learn
Smart Pointers and Interior Mutability
Cell, RefCell and Interior Mutability
After this lesson you can write &self methods that update state, pick the right wrapper for it, and avoid the one mistake that makes RefCell panic in real programs. In the intermediate tier you used RefCell and Mutex as tools; here you see what they share and where they bite.
One primitive underneath
A &T normally promises that nobody mutates the value. UnsafeCell<T> is the one type the compiler exempts from that promise, and every interior-mutability type wraps it and adds its own rule so that aliasing stays safe:
| Type | The rule | Thread-safe |
|---|---|---|
Cell<T> | values are copied or moved in and out, a reference is never handed out | no |
RefCell<T> | borrows are counted at run time, a violation panics | no |
Mutex<T>, RwLock<T> | a lock, other threads wait | yes |
| atomics | single hardware operations | yes |
Cell: values in, values out
A Cell never lends a reference, so there is no borrow to track and nothing can panic. get needs T: Copy; for anything else use take (leaves Default behind) or replace.
struct Sensor { level: i32, reads: Cell<u32> }
impl Sensor {
fn read(&self) -> i32 {
self.reads.set(self.reads.get() + 1);
self.level
}
}
// s.read(); s.read(); println!("{} {}", s.read(), s.reads.get()); // 7 3
let slot = Cell::new(Some(String::from("job-9")));
println!("{:?} {:?}", slot.take(), slot.take()); // Some("job-9") None
Cell::new(String::new()).get() is rejected: String : std::marker::Copy is not satisfied.
RefCell: a counter and a guard
borrow and borrow_mut return guards (Ref, RefMut) that decrement the cell's counter in Drop. The check can be asked without panicking:
let log = RefCell::new(vec![1, 2]);
let first = log.borrow();
println!("{}", log.try_borrow_mut().is_err()); // true
drop(first);
println!("{}", log.try_borrow_mut().is_ok()); // true
Ref::map narrows a guard to one field or element without cloning, and the cell stays borrowed for as long as the narrowed guard lives:
fn first(&self) -> Ref<'_, String> { Ref::map(self.names.borrow(), |v| &v[0]) }
The trap: a temporary guard that lives too long
A temporary created in the scrutinee of match, if let or for lives until the end of the whole statement, arms included. This memoizing get looks fine and panics:
match self.memo.borrow().get(&n) { // the Ref lives to the closing brace
Some(v) => *v,
None => {
let r = self.get(n - 1) + self.get(n - 2);
self.memo.borrow_mut().insert(n, r); // panics
r
}
}
thread 'main' panicked at 'already borrowed: BorrowMutError'
Copy the answer out first so the guard is gone before anything else runs:
let cached = self.memo.borrow().get(&n).copied(); // guard dropped at the `;`
match cached { Some(v) => v, None => { /* same arm, no panic */ } }
With that fix get(10) returns 55 after 19 calls, counted in a Cell<u32> on the same &self. The rule: never hold a guard across a call that might reach the same cell, and recursion always might.
The thread-safe siblings
Cell and RefCell update plain memory, so two threads sharing &Cell would race. They are Send but not Sync:
error[E0277]: `std::cell::Cell<u32>` cannot be shared between threads safely
Mutex and RwLock block instead of panicking, and a counter needs only an atomic: four threads each doing hits.fetch_add(1, Ordering::SeqCst) a thousand times on an Arc<AtomicUsize> end at 4000. Use SeqCst until you have a reason not to.
Common mistakes
- Looping over
self.rules.borrow().iter()and calling something that doesborrow_mut()inside: it panics withalready borrowed. - Re-reading the list by index on every iteration to avoid the guard: items added during the loop run in the same pass, so the output gains lines.
- Raising a
Cellcounter on the way in and skipping the lower on an earlyreturn: the counter drifts and later calls misbehave. - Calling
geton aCellof a non-Copytype: compile error, usetakeorreplace.
Your exercise
Write emit for an event bus whose handlers can register new rules while it runs. Everything else is in the starter. The likeliest mistake is keeping the rules guard alive while an action runs.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…