We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Smart Pointers and Interior Mutability step 5 of 26
`Rc<T>`: shared ownership by reference counting
Run a tiny acquire/release protocol over shared definitions and report both the answers and the sharing.
pub fn share_report(defs: Vec<Vec<i64>>, refs: Vec<usize>) -> (Vec<i64>, Vec<usize>)
Wrap each definition in an Rc<Vec<i64>>. Then walk refs in order and
apply the first rule that matches:
| reference | action | pushes |
|---|---|---|
usize::MAX |
release: drop the most recently acquired handle, if any |
-1 |
r < defs.len() |
acquire: keep a new handle to definition r |
the sum of definition r |
| anything else | nothing |
-1 |
Return (the pushed values, the final strong count of every definition) —
measured with every handle you are still holding alive.
usize::MAX is 18446744073709551615 and arrives from the test data
verbatim; the JSON layer does not round it through a float.
Why Rc exists
Everything you have written so far had exactly one owner. That works astonishingly often, and where it works you should keep doing it. But some shapes genuinely have no single owner:
- a node with two parents in a DAG,
- a string interned once and referred to from a thousand places,
- a configuration table read by several independent subsystems,
- a cache entry that must outlive whichever request happened to create it.
You could give one of them ownership and hand the rest & references — but
then you need a lifetime that outlives all of them, and you are back to
designing a scope around a data structure. Rc<T> — reference counted —
says instead: there are several owners, and the value dies when the last one
does.
The mechanism, exactly
Rc::new(v) heap-allocates a small block containing two counters and v
itself. The Rc handle you hold is one pointer to that block.
Rc::clone(&a) copies the pointer and increments the strong counter. It
does not touch v. That is why cloning an Rc<Vec<i64>> of a million
elements is a couple of instructions while cloning the Vec itself is a
million-element copy.
Dropping an Rc decrements the counter. When it reaches zero, v is dropped
and the block is freed. Rc::strong_count(&a) reads the counter, which is
what makes the sharing observable — and therefore testable.
Two conventions worth adopting now. Write Rc::clone(&a) rather than
a.clone(), even though they are identical: the associated-function form
makes it visible at the call site that you are copying a pointer, not the
data. Clippy has a lint for it, clone_on_ref_ptr — and it is
allow-by-default, so the gate here will not enforce it and plenty of
production code writes .clone(). Learn the reasoning, not the rule.
And note that the counter increment is a plain, non-atomic += 1. That is
exactly why Rc is neither Send nor Sync, and why the cross-thread
version is called Arc and costs more. Try to move an Rc into a thread and
the compiler stops you; that is the single best demonstration in the language
of a data race caught at compile time.
The dead end you should walk into on purpose
The starter contains a first draft that does not compile:
let owned: Vec<i64> = *defs[r];
error[E0507]: cannot move out of an `Rc`
|
| let owned: Vec<i64> = *defs[r];
| ^^^^^^^^ move occurs because value has type
| `Vec<i64>`, which does not implement `Copy`
Of course it cannot. If it could, the other handles would be pointing at a
hole. Note the contrast with the previous items: *boxed does move the
value out, because a Box has exactly one owner and the compiler
special-cases it. Rc has many, so * on an Rc can only ever borrow. You
do not need the value out of the Rc at all here — defs[r].iter() derefs
straight through.
Now spend one minute on the other dead end, because it sets up the second half of this track. Add a line that tries to change a shared definition:
defs[r].push(0);
error[E0596]: cannot borrow data in an `Rc` as mutable
= help: trait `DerefMut` is required to modify through a dereference,
but it is not implemented for `Rc<Vec<i64>>`
Read that carefully: Rc hands out &T and only &T. It cannot hand out
&mut T, because it has no idea how many other handles exist. So an Rc on
its own gives you sharing without mutation — and a very large fraction of
the rest of this track is about what to do when you need both. Do not go
looking for the answer yet. Delete the line and finish the problem.
Three lints that live here
-
rc_clone_in_vec_init(default-on).vec![Rc::new(x); 3]does not make three values.vec![v; n]clonesv, and cloning anRcclones the handle — so you get one allocation with three handles pointing at it, and every “independent” slot aliases. Build the vector with a loop or(0..3).map(|_| Rc::new(x.clone())).collect(). -
redundant_allocation(default-on).Rc<Box<T>>is two hops to reach one value, and theBoxbuys nothing anRcdid not already provide. -
rc_buffer(allow-by-default).Rc<String>andRc<Vec<T>>are also double indirection — you will meet the fix,Rc<str>andRc<[T]>, later in this track. This problem usesRc<Vec<i64>>deliberately, because the definitions are what you were handed.Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.