Skip to content

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

Easy Primitives

matches! and the lints that push you toward it

Parse shape descriptors and count four overlapping categories.

pub fn count_shapes(descriptors: Vec<String>) -> Vec<u64>

Return exactly four counts, in order:

  1. round — how many are Circle
  2. straight — how many are Square or Rect
  3. big — how many are a Circle with radius strictly greater than 10.0
  4. unknown — how many failed to parse

Descriptors look like circle 5, square 3, rect 2 3. Anything else, including a numeric field that does not parse, is Unknown.

The right tool for “is this shaped like X?”

Given a value of a sum type, the question “which variant is this?” comes up constantly, and a match is a clumsy way to ask it:

match shape {
    Shape::Circle(_) => true,
    _ => false,
}

That is nine tokens of ceremony around one predicate, and clippy’s match_like_matches_macro (on by default) will reject it. The tool is:

matches!(shape, Shape::Circle(_))

matches! takes a value and a pattern and gives you a bool. It accepts everything the pattern language offers:

matches!(s, Shape::Square(_) | Shape::Rect(_, _))     // or-pattern
matches!(s, Shape::Circle(r) if *r > 10.0)            // guard
matches!(c, 'a'..='z')                                // range

which makes it drop straight into a .filter(...) without a closure body.

Your first useful macro

matches! is macro_rules! — an ordinary library macro defined in std::macros, not a compiler builtin. That is worth noticing early. In many languages macros are an exotic, dangerous corner; in Rust they are part of the everyday surface, and vec!, format!, println!, write!, assert! and matches! are all just library code that takes syntax instead of values.

Why a macro at all? Because a pattern is not a value. You cannot pass Shape::Circle(_) to a function — there is no such thing at run time. A macro operates before type checking, on the syntax, so it can accept a pattern where an argument would be impossible.

What matches! cannot do

It cannot bind. matches! gives you a bool and nothing else, so the moment you need the payload it is the wrong tool — reach for if let or a match. That is not a defect; a predicate that also smuggled a value out would be a worse predicate.

The neighbours

redundant_pattern_matching (on by default) is the same lesson for Option and Result: if let Some(_) = o { .. } is o.is_some().

equatable_if_let (nursery, allow-by-default) suggests == in place of if let where the pattern has no bindings and the type is PartialEq. It is a stylistic call and reasonable people disagree; the gate does not enforce it.

manual_is_variant_and (allow-by-default) spots opt.map(|x| pred(x)).unwrap_or(false) and offers opt.is_some_and(pred).

A small idiom in the parser

Look at r.parse().map_or(Shape::Unknown, Shape::Circle). Shape::Circle is being passed as a function — a tuple variant’s name is a constructor function of its payload. Same trick as .map(Some) or .map(Ok).

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