We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 18 of 24
Implementing a reference-counted pointer
Repair MyRc<T>, a single-threaded reference-counted pointer. Cloning a handle
must share the allocation and increment its count without cloning T.
Dropping the last counted handle must destroy the value and free the allocation
exactly once.
Keep the supplied representation and public methods:
struct Inner<T> { strong: Cell<usize>, value: T }
pub struct MyRc<T> { ptr: NonNull<Inner<T>>, _marker: PhantomData<Inner<T>> }
pub fn rc_script(ops: Vec<String>) -> Vec<i64>
Implement Clone manually for every T, without a T: Clone bound. Preserve
new, strong_count, Deref and Drop, correcting them if needed. Keep the
type neither Send nor Sync. Use the provided script interpreter to exercise
the pointer; do not replace it with simulated counts.
Script behavior
There are sixteen slots, numbered 0 through 15. Each holds either no handle
or one MyRc<String>. Return one integer for every input operation.
| Operation | Result |
|---|---|
new I S |
Store a fresh allocation containing S in slot I; return 1. |
clone I J |
Clone I, record its new count, then replace slot J; return the recorded count. |
drop I |
Remove the handle and return its allocation's count minus one. |
count I |
Return the current count for that slot's allocation. |
read_len I |
Return the stored string's UTF-8 byte length through Deref. |
| Unknown command | Return -2. |
A required invalid slot index or empty source slot gives -1. Replacing an
occupied slot drops its previous handle. The clone result is recorded before
that drop, so it can differ from a subsequent count:
operations: ["new 0 hi", "clone 0 0", "count 0", "read_len 0", "drop 0"]
results: [1, 2, 1, 2, 0]
Preserve the supplied parser. It splits at the first two literal ASCII spaces,
without trimming or collapsing spaces. The remainder is S for new, so
spaces inside it are preserved; a missing remainder creates an empty string.
Slot fields use Rust's usize parser, including an optional leading +.
Missing, malformed and out-of-range indices are invalid. For clone, the
entire remainder is the destination index. drop, count and read_len
ignore any remainder after their index. Empty input returns an empty result.
Count ownership, not pointer copies
A fresh allocation starts at one. Each successful clone adds one ownership obligation, and each destructor releases one. A forgotten handle does not run its destructor, so its count remains and can keep the allocation alive.
The starter derives Clone. That copies its pointer and marker without updating
the shared count, and can impose an unnecessary T: Clone bound. Dropping one
of the resulting handles can free storage still referenced by another. Further
access is undefined behavior; a particular crash or double free is not a
guaranteed symptom.
Write a manual Clone that checks for overflow before incrementing, then returns a handle with the same pointer. It must not clone the stored value.
Prevent overflow without corrupting the count
Repeatedly cloning and forgetting handles can increase the count beyond the number of handles still accessible to the program. A safe abstraction must prevent that counter from wrapping even if reaching the limit is impractical in an ordinary test.
The reference uses assert!(count != usize::MAX) before changing the count.
If that assertion unwinds, the original count is intact and no new handle has
been returned. Catching this panic therefore does not leave a wrapped count.
Aborting is another possible policy. The critical requirement is to avoid
continuing with an invalid ownership count.
Free only after the last release
If the old count is one, reconstruct the Box from its original pointer and drop it. Otherwise decrement the count. Decrementing first and then checking for zero is also valid if all accesses happen before deallocation; the starter's decrement-before-check order is not itself the bug.
Box::leak leaves the allocation alive and returns a mutable reference with
a suitable chosen lifetime. It does not require every T to be 'static.
Box::from_raw takes ownership back; call it once, using the pointer from the
original allocation. Never read the counter after freeing it. See the
Box ownership APIs.
inner(&self) and Deref::deref(&self) return references tied to the handle's
borrow. Do not introduce an unconstrained output lifetime that lets a caller
use the value after its owners are gone.
Keep the single-threaded boundary
The shared counter uses Cell<usize>, not an atomic operation. NonNull makes
the supplied handle neither Send nor Sync, so no manual implementation is
needed. Do not add unsafe Send/Sync implementations. Switching to a concurrent
pointer requires a separate synchronization and lifetime argument, not merely
replacing the counter's type.
Keep the PhantomData marker as part of the supplied ownership representation. It does not free memory or adjust the count; the implementation must do that. See NonNull and the standard library's Rc overview.
Check more than the reported counts
The script checks observable counts and reads. It does not prove that the
allocation is freed, that overflow is handled, or that unsafe code is sound.
Review those properties separately. A destructor-counting value is useful for
testing that the last drop destroys T once, without requiring T: Clone.
Passing this exercise requires compilation, test cases and Clippy with
warnings denied.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.