We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Smart Pointers and Interior Mutability step 25 of 26
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.
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.