Skip to content

← Smart Pointers and Interior Mutability step 7 of 26

Hard Primitives

Rc::make_mut: copy-on-write shared data

Edit a vector through Rc::make_mut, retain a snapshot, and report whether the two Rc handles still refer to the same allocation.

pub fn cow_edits(
    initial: Vec<i32>,
    ops: Vec<(usize, i32)>,
    snapshot_after: usize,
) -> (Vec<i32>, Vec<i32>, bool)

Put initial in a mutable Rc<Vec<i32>> handle. Process operations in order. Each (index, delta) adds delta to that element through Rc::make_mut. Ignore out-of-range indices without calling make_mut.

Take the snapshot after processing min(snapshot_after, ops.len()) operations, counting ignored operations too. Use Rc::clone to retain a second handle to the same allocation, and keep it alive until the end. If snapshot_after is zero, take it before the first operation. If it is at least the operation count, take it after the last one.

Return copies of the final vector and snapshot vector, followed by Rc::ptr_eq(&data, &snapshot). The boolean describes the Rc allocations before creating the returned Vec copies, not the storage of those copies. Tested additions stay within i32.

Follow one example

Start with [1, 2, 3], apply [(0, 10), (1, 20)], and take the snapshot after one operation:

  1. The first operation produces [11, 2, 3].
  2. Rc::clone creates a snapshot handle sharing that vector.
  3. The second operation uses make_mut, separating the editable vector from the snapshot before changing element 1.

Return ([11, 22, 3], [11, 2, 3], false).

What make_mut does

Rc::make_mut needs a mutable borrow of the Rc handle. For the Vec<i32> in this exercise, it returns a mutable reference to that vector.

With one strong owner and no Weak handles, it can return mutable access without cloning the vector. With multiple strong owners, it clones the vector into a separate Rc allocation and updates this handle to use it. Other strong handles keep the previous vector.

There is a third case outside this exercise: with one strong owner and existing Weak handles, make_mut detaches those Weak handles without cloning the contained value. They can no longer be upgraded. Therefore a strong count of one alone does not imply the allocation stays unchanged.

Copy-on-write delays copying until mutable access is requested. It does not inspect what you do with the returned reference. A valid operation with delta zero still calls make_mut and separates shared Rc handles, even though the vector's values remain equal.

Once the editable handle has separated from the snapshot, later edits do not clone it again: it is now the sole strong owner and this exercise creates no further snapshots or Weak handles.

Snapshot values and shared mutation are different goals

Use copy-on-write when each owner should be able to edit its own version while retaining earlier values elsewhere. For Vec<i32>, cloning copies the elements into an independent buffer. More generally, Clone follows the contained type's implementation and may itself preserve shared data.

Rc<RefCell<T>> serves a different purpose: holders can access one mutable value through runtime borrow checks. Conflicting borrow_mut calls can panic; try_borrow_mut reports a borrow error instead. make_mut does not have that borrow-check failure, but it is not universally panic-free: a contained type's Clone implementation can panic.

Neither approach is always faster. Consider whether edits must be visible to other holders, the cost of cloning, and how often sharing is broken. The number of other owners does not multiply the single value clone performed by one make_mut call.

Preserve sharing for ignored operations

Check the index before calling make_mut. If every operation after the snapshot is out of range, the handles must remain shared. Empty input also stays shared because it has no valid indices.

Conversely, any valid operation after the snapshot makes the boolean false, including a zero delta or changes that later cancel each other. Rc::ptr_eq checks allocation identity, not equality of vector contents.

Clone the Rc for the snapshot, not the Vec inside it. The owned Vec copies belong at the return boundary, after measuring allocation identity.

Repair the starter

Declare data mutable so it can be passed as &mut data; the starter's immutable binding triggers E0596. Fill in the snapshot assignment with a cloned Rc handle. The fallback after the loop handles snapshots at or beyond the operation count, including an empty operation list.

The submission must compile, match both vectors and the sharing flag, and pass Clippy with warnings denied.