We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Errors Are Values step 6 of 24
Mutating an Option in place
Run a tiny memoising cache and report, for every operation, what the cache was holding — plus a final count of how many times the expensive computation actually ran.
pub fn run(ops: Vec<String>) -> Vec<i32>
Internally you keep cache: Option<i32> and computations: i32. Each op
pushes exactly one number onto the output; after the last op, push
computations.
| op | behaviour | pushes |
|---|---|---|
"get" |
return the cached value, computing it only if absent | the value |
"clear" |
empty the cache |
the value that was there, or -1 |
"set:N" |
put N in the cache |
the value that was there, or -1 |
| anything else | ignored |
-1 |
The computation, when it runs, is computations += 1 followed by
computations * 10. So the first computed value is 10, the second 20, the
third 30. That numbering is what makes “did it recompute?” an assertion
rather than a guess.
Worked example — ["get", "get", "clear", "get"]:
-
get→ cache empty, compute (computations = 1) → push10 -
get→ cache hit, no computation → push10 -
clear→ cache held10→ push10, cache now empty -
get→ cache empty, compute (computations = 2) → push20 -
end → push
2
giving [10, 10, 10, 20, 2].
Why the starter is wrong
The starter uses Option::insert, and that is a genuine and very common
confusion:
-
insert(v)sets the slot tovunconditionally, overwriting whatever was there, and returns&mut Tpointing at the new value. -
get_or_insert(v)sets it only if it wasNone, and returns&mut Tto whatever is now there. -
get_or_insert_with(f)is the same, but only callsfwhen it is actually going to insert — this is theunwrap_or_elsedistinction again, and it is the whole reason a cache is a cache.
Its "clear" and "set:N" arms have the other bug: they reassign the slot
(cache = None) and so throw away the old value they were supposed to report.
The four moves
let mut slot: Option<i32> = Some(1);
slot.take() // -> Some(1), slot is now None
slot.replace(7) // -> the old value as an Option, slot is now Some(7)
slot.insert(9) // -> &mut 9, unconditionally overwrites
slot.get_or_insert_with(f) // -> &mut T, calls f() only if it was None
Option::take is the one to remember. It is std::mem::replace(&mut slot, None) with a nicer name, and it is the canonical way out of E0507 when
your T is not Copy:
struct Node { next: Option<Box<Node>> }
// E0507: cannot move out of `node.next` which is behind a mutable reference
let tail = node.next;
// fine: `take` swaps None in, so `node` is still complete afterwards
let tail = node.next.take();
That pattern is how linked structures, builders and state machines get
restructured in Rust. Beginners who never learn it reach for .clone() and
then wonder why their tree costs a full copy to walk.
Because this problem’s T is i32 (which is Copy), you get to practise
the move without fighting the borrow checker. The methods are identical for
non-Copy types — that is the point of doing it here first.
The &mut return, and the NLL stumble
get_or_insert_with returns &mut T, not T. That reference borrows the
whole Option for as long as you hold it, so this fails:
let slot = cache.get_or_insert_with(|| compute());
let hit = cache.is_some(); // E0502: `cache` is already mutably borrowed
out.push(*slot);
The fix is almost always to stop holding the reference: *cache .get_or_insert_with(…) copies the value out and the borrow ends at the end of
that statement. If T were not Copy you would .clone() it, or restructure
so the borrow does not have to outlive the statement.
E0499 is the sibling error — two &mut to the same thing at once, which
is what you get if you call two of these methods and keep both results.
Notes
-
take_if(p)(1.80) takes the value only if the predicate says so;get_or_insert_default()(1.83) isget_or_insert_with(Default::default). -
mem_replace_option_with_someis the clippy lint forstd::mem::replace(&mut opt, Some(v))— sayopt.replace(v). -
Option<Option<T>>(clippy::option_option) almost always means a design mistake: nobody can remember which layer means what. Use a three-variant enum with named variants instead. -
-1is a sentinel for “the cache was empty”. Sentinels are exactly whatOptionexists to replace — it is used here only because the harness speaks JSON arrays of integers, and it is worth noticing that you had to give one up to talk across that boundary.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.