Skip to content

← Smart Pointers and Interior Mutability step 5 of 26

Medium Primitives

Rc<T>: shared ownership by reference counting

Process acquire and release operations over shared vectors, then report their sums and remaining strong-reference counts.

pub fn share_report(defs: Vec<Vec<i64>>, refs: Vec<usize>) -> (Vec<i64>, Vec<usize>)

Wrap each input definition in its own Rc<Vec<i64>>, including definitions whose contents happen to be equal. Keep those original handles alive. Use a stack for additional handles acquired by the protocol.

Process one operation at a time

For every r in refs, apply the first matching rule:

Condition Action Value to append
r == usize::MAX release the most recently acquired handle, if any -1
r < defs.len() acquire and retain another Rc handle to definition r sum of that vector
otherwise leave all held handles unchanged -1

A release on an empty stack is allowed. An empty definition sums to zero. Tested sums fit in i64. A legitimate sum can equal -1, so an output value alone does not identify which kind of operation occurred.

Return the output values in operation order and one strong count per definition in definition order. Measure counts before dropping the remaining acquired handles. Each count is one original handle plus the number of acquired handles to that definition still on the stack.

The fixtures use a 64-bit runtime, where the release marker usize::MAX is 18446744073709551615. Use the constant in Rust rather than a hard-coded decimal. This numeric value is target-dependent.

Work through an example

With definitions [[5], [7]] and operations [0, 1, 0, usize::MAX, 9]:

  • The first three operations acquire handles for definitions 0, 1 and 0, producing values [5, 7, 5].
  • The release removes the last handle, which refers to definition 0.
  • The invalid index 9 changes nothing. Both final operations append -1.

Return ([5, 7, 5, -1, -1], [2, 2]). Each definition has its original handle and one retained acquisition. With no operations, every count is one. With no definitions, all operations produce -1 and the count vector is empty.

Clone the handle, not the vector

Rc provides shared ownership within one thread. Rc::clone(&handle) creates another handle to the same value and increments its strong count. It does not clone the vector's elements. Cloning the Vec itself instead would create independent contents and would not increase this Rc count.

handle.clone() has the same behavior when handle is an Rc. The qualified Rc::clone spelling makes that choice explicit. The optional clone_on_ref_ptr lint prefers it, but is not enabled by default.

Dropping a strong handle decrements the count. The value is dropped when the last strong owner is released. Weak handles, if present, can retain the allocation's bookkeeping after the value is gone. This exercise creates no Weak handles.

Rc's reference counting is not atomic, and Rc is neither Send nor Sync. Arc supports atomic reference counting for cross-thread ownership when its contents satisfy the required bounds. That distinction is about access guarantees, not a fixed instruction count or universal speed ratio.

Repair the move in the starter

The starter tries to move a Vec out through an Rc dereference:

let owned: Vec<i64> = *defs[r];

This causes E0507. Dereferencing the Rc provides shared access; moving out a non-Copy Vec could leave other handles without a valid value. You only need to read it: defs[r].iter().sum() borrows its elements.

Directly calling defs[r].push(0) would also fail because Rc does not implement DerefMut. This does not mean mutation is impossible in every Rc design: get_mut supports unique access, and interior-mutability types support other access patterns. Neither is needed for this read-only task.

Release means dropping the returned handle

Vec::pop removes and returns an Option<Rc<_>>. It does not by itself destroy a handle that you keep in a local variable. In this protocol, discard or explicitly drop that result so the strong count decreases before the final measurement.

Check the release marker before treating an out-of-range index as invalid. Otherwise release would append the right value but leave ownership counts wrong. Keep repeated acquisitions: every successful acquisition adds one handle, even if the top of the stack already refers to that definition.

The submission must compile, match values and counts, and pass Clippy with warnings denied.