Skip to content

← Ground Rules: Values, Types, Control Flow step 21 of 24

Medium Primitives

Slices &[T]: the shape that unifies arrays and Vec

Find the largest sum of any k consecutive elements.

pub fn max_window_sum(values: &[i32], k: usize) -> Option<i32>

Return None when no window of that size exists — when k is 0, and when k is longer than the slice.

([1, 2, 3, 4], 2) gives Some(7). ([1], 2) gives None. ([5], 1) gives Some(5). ([1, 2], 0) gives None.

The starter compiles and passes clippy. It panics on one of the tests, and finding out why is the exercise.

A slice is a pointer and a length

&[T]      // shared slice: read many
&mut [T]  // exclusive slice: write one

A &[i32] is 16 bytes on a 64-bit machine — a pointer to the first element and a count — which makes it a fat pointer: a reference that carries extra information about what it points at. size_of::<&[i32]>() == 16, against 8 for an ordinary &i32.

Because it is only a view, a slice can name:

  • the whole of a Vec: &v
  • the whole of an array: &arr
  • any sub-range of either: &v[2..7]
  • a string’s bytes, a boxed slice, a stack buffer…

and every function that accepts &[T] works with all of them for free. That is what “take &[T], not &Vec<T>“ buys, stated positively.

The coercion from &Vec<T> to &[T] — and from &[T; N] to &[T] — happens automatically, through Deref. You will almost never write it out. It is worth knowing it is there, because it is the same machinery that lets a &String become a &str, which is item 1.23.

windows, and the guard it needs

slice.windows(k) yields every contiguous run of k elements, overlapping, left to right:

[1, 2, 3, 4].windows(2)   // [1,2], [2,3], [3,4]

Two behaviours you must handle, and they are different:

  • If k is larger than the slice, windows yields nothing at all. That is not an error; the iterator is simply empty, and max() on an empty iterator is None. So this case already works.
  • If k is zero, windows panics: “window size must be non-zero”. There is no sensible empty window, and the method chose to be loud rather than invent an answer.

That asymmetry is why the None guard in this function is load-bearing rather than defensive. Add the check before you ask for windows, not after.

Its non-overlapping sibling is chunks(k), which splits into consecutive blocks and leaves a short one at the end; chunks_exact(k) drops the remainder and is faster. windows has no windows_exact, because every window is already exact.

Option as the return type

Returning Option<i32> rather than, say, 0 or i32::MIN is the honest signature: “there may be no answer” is a real state of the world for an empty input, and the type says so. Every caller is then forced to decide what to do about it, which is the entire value proposition.

Note that Iterator::max already returns Option<T> for exactly this reason — the maximum of nothing is not a number. So the last line of the happy path needs no wrapping.

A note on inference

w.iter().sum() cannot be inferred on its own: sum is generic over its output and there are several types it could produce. Either annotate the closure’s return, or use the turbofish — sum::<i32>() — to say which. E0282: type annotations needed is what you get if you say nothing, and you met it in item 0.2.

Loading visualization…