Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 14 of 26

Medium Primitives

Refutability, and every place a pattern can appear

Compute the centroid — the arithmetic mean — of a list of 2-D points.

pub fn centroid(points: Vec<(f64, f64)>) -> (f64, f64)

An empty list gives (0.0, 0.0). Round each coordinate to six decimal places before returning, so the answer does not depend on floating-point noise.

match is not where patterns live

Learners are taught patterns as “the thing on the left of =>“ and never find out how far the feature reaches. Every one of these is the same pattern language:

let (x, y) = point;                       // let binding
fn scale((x, y): (f64, f64)) -> f64 {}    // function parameter
for (i, (a, b)) in v.iter().enumerate()   // for-loop binding
|(x, y)| x * y                            // closure parameter
while let Some(top) = stack.pop()         // while-let
if let Ok(n) = text.parse::<i64>()        // if-let

The solution to this problem must use at least three of them: destructure in a helper’s parameter list, in the for binding, and in a let on the result.

Refutable and irrefutable

The rule that unifies all of it is a single property of a pattern:

  • An irrefutable pattern matches every possible value of its type. (x, y) matches every (f64, f64). Point { x, y } matches every Point. A bare name matches everything.
  • A refutable pattern can fail. Some(n) does not match None. [a, b] does not match a three-element slice. 42 does not match 7.

And then the rule: positions that have nowhere to go when matching fails require an irrefutable pattern. A let, a function parameter and a for binding are all such positions — there is no “else” branch for them to jump to. A match arm, an if let and a while let all do have somewhere to go, so they accept refutable patterns.

The starter breaks the rule on purpose:

error[E0005]: refutable pattern in local binding
note: `let` bindings require an "irrefutable pattern", like a `struct` or
      an `enum` with only one variant
help: you might want to use `let else` to handle the variant that isn't matched

Notice what rustc did there. It did not just refuse; it named the concept (irrefutable pattern), explained the constraint, and wrote out the exact fix. rustc’s suggestions are teaching material, and reading the whole message is a habit that pays for itself many times over. (let ... else is the very next item in this track.)

The exception people trip on

Or-patterns are not allowed at the top level of a let or a function parameter, even when every alternative binds the same names. let A(n) | B(n) = value; is rejected regardless of whether the alternatives are jointly total, because refutability is checked per-alternative, not for the group.

A lint you will meet

infallible_destructuring_match fires on a match used where a let would do — the mirror image of this item’s lesson. If a pattern is irrefutable, match adds nothing but indentation.

Floating point across the wire

Rounding to six decimals is not decoration. (1.0 + 2.0 + 4.0) / 3.0 is 2.3333333333333335 and the test compares against 2.333333. Round on the way out — that is a habit worth having any time floats cross a boundary you do not control.

Remember the grade is compile + tests + clippy -D warnings.