Skip to content

← Errors Are Values step 6 of 24

Medium Primitives

Mutating an Option in place

Implement a cache that stores one optional integer. Process each operation, report its result, and append the number of computations performed.

pub fn run(ops: Vec<String>) -> Vec<i32>

Start with cache = None and computations = 0. Each operation appends one integer, followed by one final integer for the computation count. Empty input therefore returns [0].

Operation Cache change Reported value
get Compute and store a value only when empty The cached value
clear Replace the cache with None The previous value, or -1
set:N Store Some(N) The previous value, or -1
Anything else None -1

Each computation increments computations, then returns computations * 10. A cache hit or set does not increment the counter. Inputs used here keep the counter and computed values within i32.

Operations are case-sensitive. Keep the supplied parser: after set:, parse an i32 without trimming whitespace. A missing, invalid, or out-of-range number stores zero.

Zero and negative numbers, including -1, are valid cached values. Consequently, a reported -1 is ambiguous: it may be a stored value, an empty-cache result, or an unknown operation. Internally, Option preserves the distinction.

Follow a cache through four operations

For ["get", "get", "clear", "get"]:

Step Result Cache afterward Computations
First get 10 Some(10) 1
Second get 10 Some(10) 1
clear 10 None 1
Third get 20 Some(20) 2

Append the final count to get [10, 10, 10, 20, 2].

Choose the operation that preserves the right value

Method Effect Return value
take() Leaves None The previous Option<T>
replace(value) Stores Some(value) The previous Option<T>
insert(value) Always stores the new value A mutable reference to the new value
get_or_insert(value) Inserts only when empty A mutable reference to the stored value
get_or_insert_with(f) Calls f and inserts its result only when empty A mutable reference to the stored value

The argument to get_or_insert is evaluated before the method runs, even on a cache hit. Put the computation inside the closure passed to get_or_insert_with so that it runs only when needed.

The starter recomputes and overwrites on every get. It also discards the previous value on clear and set. Use take and replace to return those old values while changing the cache.

Taking ownership without cloning

take also works when the contained value is not Copy. It moves the value out while leaving a valid None behind:

struct Pending {
    message: Option<String>,
}

impl Pending {
    fn take_message(&mut self) -> Option<String> {
        self.message.take()
    }
}

fn main() {
    let mut pending = Pending { message: Some(String::from("ready")) };
    assert_eq!(pending.take_message().as_deref(), Some("ready"));
    assert_eq!(pending.take_message(), None);
}

Returning self.message directly from that method would attempt to move a non-Copy field out through a mutable reference and produce E0507. take transfers ownership without cloning the string.

Using the returned mutable reference

get_or_insert_with returns &mut i32. Dereferencing it copies the integer for the output. You can then use the cache again without retaining that reference.

If you keep the reference and use it later, an intervening access to the cache may conflict with its mutable borrow. End the reference's use first, or restructure the accesses. A non-Copy value does not automatically require cloning; the right approach depends on whether the caller needs to borrow, move, or duplicate it.

A passing submission must compile, pass the tests, and pass clippy -D warnings.

See the Option methods for these mutation operations.