We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Smart Pointers and Interior Mutability step 11 of 26
`RefCell<T>`: borrowing checked at runtime
Two functions over a RefCell<Vec<i64>>.
pub fn apply_ops(initial: Vec<i64>, ops: Vec<String>) -> Result<Vec<i64>, String>
pub fn safe_probe(v: Vec<i64>) -> Vec<bool>
apply_ops
Put initial in a RefCell<Vec<i64>> and run the ops in order, stopping at
the first error.
| op | effect | error |
|---|---|---|
push <n> |
append n |
bad op: <op> if n does not parse |
pop |
remove the last element |
pop on empty |
scale <n> |
multiply every element by n |
bad op: <op> |
append_sum |
append the current sum | — |
mirror |
append the current contents reversed | — |
require <n> |
check the sum equals n |
require: want <n>, got <s> |
| anything else | — |
bad op: <op> |
So [1,2,3] after mirror is [1,2,3,3,2,1], and after append_sum it is
[1,2,3,6].
safe_probe
Put v in a RefCell<Vec<i64>> and return exactly six booleans, produced
by this exact script:
-
take a shared guard
a; is the vector empty? -
still holding
a— doestry_borrow()succeed? -
still holding
a— doestry_borrow_mut()succeed? -
drop
a, take an exclusive guardb; are all elements>= 0? -
still holding
b— doestry_borrow()succeed? -
drop
b— doestry_borrow_mut()succeed?
What RefCell actually does
Cell was sound because it never let a reference to its interior escape.
That makes it useless for a Vec: you cannot call .push() on something you
cannot get a reference to.
RefCell buys back the references by checking the borrow rules at runtime
instead of at compile time. It holds a counter alongside the value:
-
borrow()→ checks nothing is exclusively borrowed, increments a shared count, returns aRef<'_, T>guard; -
borrow_mut()→ checks nothing is borrowed at all, marks it exclusive, returns aRefMut<'_, T>guard; -
each guard’s
Droprestores the counter.
The rule enforced is exactly the compile-time rule — many shared or one
exclusive — and the enforcement is a comparison and a branch, one usize per
cell. The difference is what happens when you break it. The compiler tells you
at build time; RefCell panics at run time, in production, in front of a
user.
On Rust 1.95 the two messages are different, and the difference tells you which direction failed:
| what failed | why | message |
|---|---|---|
borrow_mut() |
something already holds a shared borrow |
RefCell already borrowed |
borrow() |
something already holds the exclusive borrow |
RefCell already mutably borrowed |
(The Book and most tutorials still quote the older strings
already borrowed: BorrowMutError and already mutably borrowed: BorrowError.
Those are out of date. The same two current strings are what
BorrowMutError/BorrowError Display as.)
The guard controls the lifetime, not the borrow expression
This is the single most useful sentence about RefCell:
The critical section lasts as long as the guard value, not as long as the expression that produced it.
let v = cell.borrow(); // guard lives until end of scope
cell.borrow().len() // guard is a temporary: dies at end of statement
Both of the starter’s panicking arms are that distinction, misapplied.
data.borrow_mut().push(peek(&data).iter().sum());
A method call evaluates its receiver first. So borrow_mut() runs, the
exclusive guard is live as a temporary for the whole statement, and then
borrow() runs inside the argument. Panic: RefCell already mutably borrowed.
for x in peek(&data).iter() {
data.borrow_mut().push(*x);
}
A for loop’s head temporaries live for the entire loop, so the shared
guard is held across every iteration. Panic on the first borrow_mut:
RefCell already borrowed.
Both fixes are the same move: end the borrow before you start the other
one. Read what you need into an owned value, drop the guard, then write.
Clippy’s redundant_clone would call that clone wasteful in code where the
borrow was not in the way; here it is load-bearing, and knowing the difference
is the skill.
E0515: you cannot outlive the guard
The starter opens with the most common RefCell compile error in existence:
fn peek(cell: &RefCell<Vec<i64>>) -> &Vec<i64> {
&cell.borrow()
}
error[E0515]: cannot return reference to temporary value
| &cell.borrow()
| ^-------------
| ||
| |temporary value created here
| returns a reference to data owned by the current function
The Ref guard is a local. Returning a reference into it would let the
caller hold a &Vec<i64> after the guard had been dropped and the borrow flag
cleared — after which someone else could take borrow_mut() and you would
have aliasing plus mutation, which is exactly what all of this exists to
prevent.
There are two honest fixes. Return an owned clone, or return the guard
itself: fn peek(cell: &RefCell<Vec<i64>>) -> Ref<'_, Vec<i64>>. The second
is usually what you want — the caller derefs it like a &Vec, and the borrow
ends when they drop it. That is the pattern every guard-returning API in std
uses, Mutex::lock included.
One more fact worth carrying
RefCell<T> is Send if T is, but it is never Sync — its counter is
a plain integer with no synchronisation. So Arc<RefCell<T>> does not
compile (E0277), no matter how much you want it to. The cross-thread
equivalent is Arc<Mutex<T>>, and clippy has a default-on lint,
arc_with_non_send_sync, that catches the attempt at the construction site.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.