Skip to content

← Smart Pointers and Interior Mutability step 25 of 26

Hard Research

Build Cell<T> and RefCell<T> from scratch

Implement small versions of Cell and RefCell using UnsafeCell. Keep the supplied command interpreters and fill in the cell and guard APIs.

pub fn cell_script(ops: Vec<String>) -> Vec<i64>
pub fn refcell_semantics(script: Vec<String>) -> Vec<String>

The interpreters use i64, but your cell types are generic. Their safe methods must also work correctly for owned values with destructors.

Cell commands

Cells A and B both start at zero. Each command produces one result.

Command Effect and result
set <c> <n> Store n; return 0
get <c> Return the current value
replace <c> <n> Store n; return the previous value
take <c> Store the default value, zero here; return the previous value
swap <c> <d> Exchange the two values; return 0
bump <c> <n> Add n through get_mut; return the new value
consume <c> Extract the value through into_inner; replace the cell with a fresh zero cell
Unknown command, wrong argument count or unknown cell Return -1

Commands and cell names are case-sensitive. The interpreters split on whitespace. A numeric argument that fails to parse as i64 becomes zero; this includes overflow. Valid bump inputs keep the sum within i64. Swapping a cell with itself leaves it unchanged. Empty command lists return empty output lists.

RefCell commands

The value starts at zero. Successful share and excl commands push a guard onto a stack. The most recently pushed guard is the top guard.

Command Result
share share ok <value> and push a shared guard, or share fail
excl excl ok and push an exclusive guard, or excl fail
write <n> Write through an exclusive top guard and log write <n>, or write fail
peek Read the top guard as peek <value>, or peek fail if none exists
release Drop the top guard and log the resulting release <state>, or release none
state Log state <n>
panic_share Temporarily call borrow; log share ok or the caught panic
panic_excl Temporarily call borrow_mut; log excl ok or the caught panic
Anything else Log ?

The two panic_ commands do not push guards onto the stack. A successful temporary borrow is dropped before the command finishes. On failure, the supplied methods produce panic: MyRefCell already mutably borrowed for borrow, or panic: MyRefCell already borrowed for borrow_mut.

What UnsafeCell permits

UnsafeCell<T> permits mutation of its contents through a shared reference to the cell. It does not permit conflicting references to the contents or unsynchronized access from multiple threads. Calling .get() is safe and returns a raw pointer; accessing the pointed-to value requires you to establish the relevant safety conditions.

A shared-reference method on MyCell must not expose a reference to its contents. get copies a T: Copy; replace transfers ownership of the old value and installs a new one. In contrast, get_mut(&mut self) may return &mut T because the caller has exclusive access to the cell. Both get_mut and the consuming into_inner can use safe UnsafeCell methods.

Replace before dropping

Do not fix the starter's raw assignment merely by enclosing it in unsafe. Assignment drops the previous value in place, and that destructor could call back into the same cell. Instead, set should replace the value first, then drop the value it removed. The cell already contains its replacement when the destructor runs, even if that destructor panics or reenters.

Implement replace with std::ptr::replace. It transfers the old value without invoking its destructor. Keep the pointer operation short: no callback or user destructor should run while the cell is being updated.

For swap, handle identical cells as a no-op. Distinct live cells holding non-zero-sized values must refer to separate payloads through this API. Zero-sized values need no bytes exchanged. ptr::swap supports overlapping pointers, but that does not remove your responsibility to preserve valid ownership of the values being exchanged.

RefCell tracks outstanding borrows

Use the counter consistently:

  • 0: no active guards.
  • A positive number: that many shared guards.
  • -1: one exclusive guard.

try_borrow returns None when an exclusive guard exists or the shared count has reached isize::MAX. Otherwise increment the count before returning a shared guard. Never let the counter overflow. try_borrow_mut succeeds only at zero and changes the state to -1. Failed attempts leave the state unchanged.

A shared guard's Drop decrements the count. An exclusive guard's Drop restores zero. Deref returns a reference tied to the guard borrow; DerefMut requires a mutable borrow of the exclusive guard. This keeps references from remaining usable after their guard is dropped.

Keep guard fields private so outside callers cannot bypass the checks. Each guard borrows its cell for its declared lifetime. Do not implement Copy or unchecked Clone for guards: copying one without registering a new borrow would break the count. Rust already rejects implementing Copy for a type with a destructor, but it cannot verify your unsafe invariants.

Leaked guards and thread safety

Safe code can call mem::forget on a guard, so safety cannot depend on its destructor running. Forgetting a shared guard leaves a positive count: other shared borrows may still succeed, but exclusive borrows are blocked. Forgetting an exclusive guard blocks both kinds of borrow. Leaking must restrict future access rather than allow conflicting references.

These types inherit !Sync from UnsafeCell; adding an unsafe Sync implementation would require synchronization that this design lacks. They can be Send when their contents are Send. Constructing an Arc<MyRefCell<T>> is legal, but the Arc does not make the cell safe to share between threads.

Write a // SAFETY: comment for every unsafe block explaining the lifetime, initialization and aliasing conditions it relies on. The scripts test observable behavior; passing them or Clippy is not a proof of soundness. Also review generic payloads, destructor callbacks and leaked guards.

Submissions must compile, pass the tests and pass clippy -D warnings.