Skip to content

← Smart Pointers and Interior Mutability step 6 of 26

Medium Primitives

Trace shared ownership with Rc

Complete an interpreter over named Rc<i64> handles. Fix the get_mut lookup and implement the unfinished unwrap and into_inner commands.

pub fn ownership_trace(script: Vec<String>) -> Vec<String>

Keep handles in a BTreeMap<String, Rc<i64>>. Process commands in order and append exactly one log line per input line.

Command Effect Log
new n v Store a fresh allocation under n, replacing any old handle n=1
clone src dst Clone src, then replace dst dst=<count before replacement>
count n Read the strong count n=<count>
read n Read the stored integer n is <value>
drop n Remove and drop the handle drop n -> <remaining count>
get_mut n d Add d if the allocation is uniquely owned get_mut n ok or get_mut n shared
unwrap n Remove the handle and try to extract the value; restore it on failure unwrap n ok <value> or unwrap n shared
into_inner n Remove and consume the handle, even on failure into_inner n ok <value> or into_inner n none
ptr_eq a b Compare allocation identity and stored values ptr_eq a b ptr=<bool> val=<bool>

Booleans are lowercase true or false. A missing handle produces n=?, n is ?, drop n -> ?, get_mut n ?, unwrap n ? or into_inner n ?, as appropriate. A clone with a missing source logs dst=? and leaves any existing destination unchanged. If either comparison handle is missing, log ptr_eq a b ?.

Parsing and replacement rules

Split each line with split_whitespace. Commands and names are case-sensitive; leading, trailing and repeated whitespace is ignored. A blank line, unknown command or wrong number of fields logs ? without changing handles.

Parse v and d as i64. Invalid or out-of-range fields become zero. Successful get_mut additions in the inputs fit in i64; overflow handling is not required. Names are whitespace-separated tokens and may be reused.

For clone, measure the strong count after creating the new handle but before dropping any old destination. This matters even when source and destination have the same name:

new a 5   -> a=1
clone a a -> a=2
count a   -> a=1

Replacing the old a drops one handle after the clone's count has been recorded. Dropping a handle is ordinary destruction, not mem::forget.

Shared mutation and consuming operations

Rc::get_mut needs &mut Rc<T>, so use the map's get_mut method to retrieve the handle. The starter uses get and receives only &Rc<T>, causing E0308. The interpreter creates no Weak handles, so one strong owner is sufficient here. More generally, a Weak handle also prevents Rc::get_mut from succeeding.

Rc::try_unwrap consumes its input. With one strong owner it returns the value; otherwise its Err contains the original handle. Restore that handle under the same name on failure. Rc::into_inner also consumes its input, but returns None when another strong owner exists and does not return the handle. Both extraction operations can succeed despite outstanding Weak handles.

For example, after new a 9 and clone a b, unwrap a logs unwrap a shared and retains both names. into_inner a then logs into_inner a none, removes a and leaves b with a strong count of one.

Identity and value are different comparisons

Two independently created Rc values can store the same integer. They compare equal by value but Rc::ptr_eq returns false. Cloned handles share an allocation, so both comparisons return true in this exercise.

Many Rc operations use associated-function syntax such as Rc::strong_count(&rc) to avoid conflicts with methods of the wrapped value. This is an API choice, not a rule that smart pointers cannot have methods. In particular, rc.clone() is available through Clone; Rc::clone(&rc) makes the handle clone explicit.

See the Rc documentation. Passing requires compilation, tests and Clippy with warnings denied.