We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Smart Pointers and Interior Mutability step 7 of 26
`Rc::make_mut`: copy-on-write shared data
Edit a shared vector, take a snapshot part-way through, and report whether the two still share one allocation.
pub fn cow_edits(
initial: Vec<i32>,
ops: Vec<(usize, i32)>,
snapshot_after: usize,
) -> (Vec<i32>, Vec<i32>, bool)
Put initial in an Rc<Vec<i32>>. Each op is (index, delta) and adds
delta to the element at index. An op whose index is out of range does
nothing at all — and, importantly, must not cause a copy either.
After exactly min(snapshot_after, ops.len()) ops have been applied, take a
shared handle — a second Rc onto the same allocation, not a copy of the
data. Keep it alive to the end.
Return (the final vector, the snapshot's vector, Rc::ptr_eq(final, snapshot)).
The tool almost nobody finds
You have just been told that Rc hands out &T and only &T. The obvious
conclusion — “so to mutate shared data I need RefCell“ — is the single most
over-applied idea in this track, and it skips a type that is better for the
most common case.
Rc::make_mut(&mut rc) -> &mut T // where T: Clone
It does exactly one clever thing:
-
if the strong count is 1, hand out
&mut Tdirectly — free, no copy; -
if the count is greater than 1, clone the value into a fresh
allocation, point this handle at the new one, and hand out
&mut Tto that.
This is copy-on-write, the same idea behind fork()‘s page tables and every
persistent data structure you have used. The name for it in Rust is Cow, and
Rc::make_mut is the version that works when the sharing is by refcount.
For mostly-shared, rarely-mutated data it dominates RefCell on every axis
you care about: no runtime borrow flags, no branch on every access, and —
crucially — it cannot panic. A BorrowMutError is a runtime failure mode
you have chosen to accept; make_mut simply has no such mode.
The surprise, and why this problem exists
With a and b sharing a Rc<Vec<i32>> holding [1,2,3]:
Rc::make_mut(&mut a).push(4);
// a == [1, 2, 3, 4]
// b == [1, 2, 3] <- b did NOT see the change
Almost everyone expects b to change. It does not, and it never will —
make_mut broke the sharing. Afterwards a and b each have a strong
count of 1 and Rc::ptr_eq(&a, &b) is false. The clone was invisible: no
syntax marked it, no error announced it, and a println! of the values would
not have told you it happened.
That is what the returned bool is for. Making the invisible clone
observable is the entire pedagogical trick here, and it is the difference
between believing you understand make_mut and actually understanding it.
When to use it and when not to
Use it when: the data is Clone, sharing is the common case, mutation is
rare, and each mutator owns its own handle.
Do not use it when:
-
The clone is expensive and the data is heavily shared. Note the cruelty
of the cost model:
make_mutcopies the whole value exactly when the count is high — that is, exactly when the data is most shared, which for a largeVecis exactly when you can least afford it. -
You need several holders to see each other’s writes. They cannot. The
moment one writes, it forks. Observer graphs, shared caches and callback
registries genuinely need
RefCell, and that is what it is for. -
You only have
&Rc<T>.make_mutrequires&mut Rc<T>— an exclusive borrow of your handle, not of the data. Any design that hands out sharedRcs from a central table cannot call it.
Getting the ptr_eq case right
Two details decide the hidden cases.
Call make_mut only when you are actually going to write. If you call it
before checking the index, an out-of-range op forks the allocation for
nothing, and the shared flag comes back false where the tests expect
true.
And the snapshot must be Rc::clone(&data) — a handle. If you reach for
data.as_ref().clone() or (*data).clone() you get a copy of the vector,
which is a different allocation from the start, which makes ptr_eq false
everywhere and defeats the whole exercise.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.