Skip to content

← Rust: zero to hero step 4 of 342

Easy Primitives

let, immutability by default, and mut

Return the largest value seen so far at every position in a slice.

pub fn running_max(values: &[i32]) -> Vec<i32>

The output has the same length as the input. At position i, it contains the maximum of input positions 0 through i, including the current value.

Input Output
[3, 1, 4, 1, 5] [3, 3, 4, 4, 5]
[-1, -9] [-1, -1]
[5, 5, 5] [5, 5, 5]
[] []

Preserve one output for every input, including duplicates. Values may be anywhere in the i32 range. The running maximum can stay the same or increase; it never decreases.

Make a binding mutable when it needs assignment

The starter tries to assign a new value to best, which was declared without mut:

let best = i32::MIN;
best = 7;

This produces E0384: an initialized immutable binding cannot be assigned another value. Add mut to permit reassignment:

let mut best = i32::MIN;
best = 7;

let introduces a variable binding. Immutability by default makes intended mutation visible in the code; it is distinct from the ownership and borrowing rules that govern access to values.

A new let can also shadow an earlier name, creating a different binding. Shadowing inside the loop would not update the outer best for the next iteration, so reassignment is the useful operation here.

The binding's mutability is not a promise that everything reachable through its value is immutable. For example, an immutable binding can hold a mutable reference. For this exercise's plain i32, focus on whether best itself may be assigned a new value.

Use mut where this implementation needs it

In the supplied loop, best changes as larger values arrive, and out needs mutable access for push. The vector does not need to be reassigned for its binding to require mut.

Rust's unused_mut lint warns when a binding was marked mutable but does not need to be. This exercise treats warnings as errors. That does not impose a fixed number of mutable bindings on every valid solution; it asks whether each declaration needs mutable access in that implementation.

Read one copied integer at a time

for &v in values {
    // Compare v with the running maximum, then append that maximum.
}

values is a borrowed slice. Iterating over it yields references to its elements. The pattern &v matches each reference and copies the i32 into v, because i32 implements Copy. The input is left unchanged. The same pattern cannot move a non-Copy value out of a shared reference.

Choose a starting value that handles negatives

Start best at i32::MIN, the smallest possible input value. Every element is greater than or equal to it. If the first element equals i32::MIN, leaving best unchanged is already correct.

Starting at zero would incorrectly report zero for an all-negative prefix. The empty case needs no special output: the loop runs zero times and the initially empty vector is returned.

For each value, update best if needed, then append best to out. Appending only when a new maximum appears would omit required positions. Passing requires compilation, correct outputs and Clippy with warnings treated as errors.