Skip to content

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

Medium Primitives

Binding with @, and what or-patterns demand of bindings

Put each number in a bucket and report both the bucket and the number.

pub fn bucket(values: Vec<i64>) -> Vec<String>

Anything up to 9 is small, 10..=99 is mid, 100 and above is large. bucket(vec![0, 50, 1000]) gives ["small:0", "mid:50", "large:1000"].

Part one: @ binds the value you just tested

The naive way to write this is a range test and a separate read of the same value — often with a guard, and always with the number written twice:

match value {
    n if (..=9).contains(&n) => Bucket::Small(value),   // clumsy
    // ...
}

The pattern language already has this. name @ pattern matches pattern and binds the matched value to name:

match value {
    n @ ..=9    => Bucket::Small(n),
    n @ 10..=99 => Bucket::Mid(n),
    n           => Bucket::Large(n),
}

Note ..=9 — a half-open range pattern, no lower bound needed. And note the last arm: a bare identifier is a binding that matches anything, which is what makes this exhaustive without a _.

@ is the pattern feature learners most reliably fail to discover. Once you have it you will use it constantly, especially in nested positions: Message::Resize { width: w @ 1..=4096, .. } tests and captures in one move.

One caveat: @ on a payload that is not Copy moves it. Bind a String with s @ .. inside a &-matched structure and you will meet E0507, “cannot move out of a shared reference”.

Part two: what or-patterns demand

payload pulls the number out of a Bucket regardless of which bucket it is:

match bucket {
    Bucket::Small(n) | Bucket::Mid(n) | Bucket::Large(n) => *n,
}

One arm, three alternatives, one binding. For that to be well-defined the compiler enforces three rules, and the starter breaks the first of them so you meet the error:

  1. Every alternative must bind exactly the same names. Miss one and you get E0408, “variable m is not bound in all patterns” — and you get it from both directions, once for the name that is missing from some alternatives and once for the name that is missing from the others. Downstream, E0381 (“used binding n is possibly-uninitialized”) piles on, because the compiler cannot promise the binding exists.
  2. The same name must have the same type in every alternative. Otherwise E0409, “variable x is bound inconsistently across alternatives”.
  3. A name may be bound only once per alternative. (a, a) is E0416, “identifier a is bound more than once”.

These are not arbitrary. A pattern’s job is to produce a set of bindings; if which alternative matched changed which bindings exist or what type they had, no code after the => could be type-checked at all.

Nesting | where the choice actually is

Beginners write Some(1) | Some(2) | Some(3). Rust lets you put the alternation exactly where the variation is: Some(1 | 2 | 3). The unnested_or_patterns lint exists to push you toward the tighter form, though it is allow-by-default so the gate will not do it for you.

redundant_pattern is on by default, and catches x @ _ — binding everything and also writing @, where a bare x says the same thing.

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

Loading visualization…