We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ground Rules: Values, Types, Control Flow step 18 of 24
Tuples and destructuring
Return the minimum, the maximum and the arithmetic mean of a slice.
pub fn min_max_mean(values: &[i32]) -> (i32, i32, f64)
[1, 2, 3] gives (1, 3, 2.0). An empty slice gives (0, 0, 0.0) — that is
this function’s specified answer, not a natural one, and specifying it is the
point of the first line of the body.
The starter does not compile. Its last line is wrong in two ways at once.
Tuples: the multi-value return
A tuple is an ordered, fixed-length, heterogeneous group of values written in parentheses. Its type is the tuple of its members’ types:
let pair: (i32, f64) = (7, 1.5);
let unit: () = (); // the empty tuple, "unit"
let single = (7,); // one-element tuple needs the comma
Access is by position with a dot and a literal number:
pair.0 // 7
pair.1 // 1.5
pair.0 only accepts a literal — you cannot index a tuple with a variable,
because each position has its own type and the type must be known at compile
time. Asking for a field that does not exist is E0609: no field on type.
Tuples are the idiomatic way to return two or three related values without
inventing a struct. Past about three fields, or as soon as the fields have names
worth writing down, use a struct instead — (i32, i32, f64) tells the reader
nothing about which is which, and MinMaxMean { min, max, mean } does.
Destructuring: your first pattern
You almost never write .0 and .1 in practice. You take the tuple apart:
let (min, max, mean) = min_max_mean(&values);
That left-hand side is a pattern, and this is your first pattern match —
arriving before the match chapter on purpose, so that when match shows up it
is a familiar idea in a new place. Patterns nest and can ignore parts:
let (min, _, mean) = triple; // `_` discards
for (i, value) in v.iter().enumerate() { … } // the loop you already wrote
let bindings, function parameters, for patterns, match arms, if let —
they all take patterns, and it is the same grammar in every one.
The two mistakes in the starter’s last line
(min, max, sum / values.len())
Wrong types. sum is an i64 and values.len() is a usize. Rust does
not implement Div<usize> for i64, so this is E0277 before it is anything
else — and even if it worked, the result would be an integer where the signature
promised an f64, which is E0308.
Wrong arithmetic. Integer division truncates toward zero, so the mean of
[1, 2] would be 1, not 1.5. Converting to floating point after dividing
would not help; you must divide in floating point.
Convert both operands to f64 and divide there.
Why the accumulator is an i64
Summing i32 into an i32 overflows, and at -O it wraps silently — you saw
that in item 1.5. There is a hidden test here whose input is
[i32::MAX, i32::MIN], whose true sum is -1, and whose mean is therefore
-0.5. Accumulate into an i64 and it is right; accumulate into an i32 and
it is not. Widening the accumulator is the cheapest correctness fix in numeric
code and it costs nothing at this scale.
About comparing the mean
The mean is a floating-point number and the grader compares it with a tolerance rather than exactly, for the reasons in item 1.8. You do not need to round.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.