Skip to content

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

Medium Primitives

Destructuring structs and nested patterns

Summarise order records. Each record is five |-separated fields: id|name|tier|status|payload.

pub fn summarise(records: Vec<String>) -> Vec<String>
order summary
gold customer, pending 1 Ada priority waiting
any other customer, pending 2 Bob waiting
shipped 3 Cy via DHL
cancelled 4 Dee cancelled (fraud)
unparseable record malformed

A parser is provided. You write summarise_one, and the constraint is that it must be one match whose arms are nested struct-and-enum patterns — no field access, no if, no second match.

Field access is the exception, not the rule

Coming from other languages you will reach for order.customer.name. In idiomatic Rust the way you read a struct is to take it apart with a pattern:

let Order { id, customer, status } = order;

and the way you read a struct inside an enum inside a struct is to write that shape out once:

Order {
    id,
    customer: Customer { name, tier: Tier::Gold },
    status: Status::Pending,
} => format!("{id} {name} priority waiting"),

Read that arm carefully. It is simultaneously: a test that the tier is Gold, a test that the status is Pending, and an extraction of id and name. Written with ifs and field accesses it is six lines and two opportunities to get the condition backwards. This is why match scales to real domain models instead of collapsing into a tower of conditionals.

.. and _ are not the same thing

Beginners conflate them constantly.

  • _ ignores exactly one thing — one field, one tuple element, one payload slot.
  • .. ignores all the remaining fields, and may appear once per pattern.

A struct pattern must account for every field. Miss one and you get E0027, “pattern does not mention field tier“ — which the starter ships, so read it. The fix is ... Name a field that does not exist and you get the mirror-image error, E0026, “does not have a field named z“. Get the arity of a tuple variant wrong and it is E0023, “this pattern has 1 field, but the corresponding tuple variant has 2”.

There is also unneeded_field_pattern, an allow-by-default lint for Customer { name, tier: _ } where Customer { name, .. } says it better.

Matching a reference

summarise_one takes &Order, so the patterns match a reference to an Order. Under Rust 2024’s binding modes, matching a &T with a non-reference pattern binds the fields by reference automatically: name comes out as &String, not String. That is what you want — moving a String out of a borrowed struct is E0507, “cannot move out of a shared reference”, and moving it out of an owned one leaves you with E0382 if you touch the rest afterwards.

One lint that will surprise you

infallible_destructuring_match is on by default. It fires on this:

let n = match wrapper { Wrapper::Only(n) => n };

A single-variant enum has nothing to choose between, so match is theatre. Write let Wrapper::Only(n) = wrapper; instead. The general principle is the same one behind single_match and match_single_binding: match is for choosing. When there is no choice, a let is the honest form.

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

Loading visualization…