Skip to content

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

Medium Primitives

Destructuring structs and nested patterns

Summarize order records using nested struct and enum patterns. Each record contains exactly five |-separated fields:

id|name|tier|status|payload
pub fn summarise(records: Vec<String>) -> Vec<String>

Return one summary per record, preserving input order. Empty input returns an empty vector.

1|Ada|gold|pending|        -> 1 Ada priority waiting
2|Bob|basic|pending|       -> 2 Bob waiting
3|Cy|gold|shipped|DHL      -> 3 Cy via DHL
4|Dee|basic|cancelled|fraud -> 4 Dee cancelled (fraud)
5|Eve|basic|lost|          -> malformed

Keep the supplied parser. It applies these rules:

  • The ID must parse as a u32. Output uses its numeric value, so leading zeroes are not retained.
  • Only the exact tier gold is special. Every other tier is basic.
  • Status must be exactly pending, shipped, or cancelled.
  • Pending orders ignore the payload. Shipped orders use it as the carrier; cancelled orders use it as the reason.
  • Fields are not trimmed. Empty names and payloads are accepted. Additional separators, missing fields, invalid IDs, and unknown statuses produce malformed.

Repair summarise_one using one match with nested patterns. For this exercise, avoid field access, if, or another match inside that function. The supplied parser and outer summarise function can retain their matches. This is a practice constraint; output tests alone do not enforce it.

Match structure and values together

A pattern can check an enum variant while binding nearby fields. The gold-pending arm binds the ID and customer name while requiring Tier::Gold and Status::Pending.

Put that specific arm before the general pending arm. Match arms are tried in order, so the general arm would otherwise consume gold orders too.

Field access is also normal Rust. Nested patterns are useful here because each branch depends on several parts of the record's structure.

Name fields or ignore the remainder

A struct pattern must account for all its fields. In the shipped arm, Customer { name } omits tier and produces E0027. Use Customer { name, .. } when the tier does not affect that summary.

_ ignores one matched value, as in tier: _. The rest pattern .. ignores the fields not otherwise named. Each individual struct pattern may have its own rest pattern, including a nested struct pattern.

This complete example shows both forms:

struct Customer {
    name: String,
    tier: u8,
}

fn main() {
    let customer = Customer { name: String::from("Ada"), tier: 1 };
    let Customer { name, .. } = &customer;
    assert_eq!(name, "Ada");

    let Customer { name: _, tier } = customer;
    assert_eq!(tier, 1);
}

The first pattern borrows the name. The second ignores the name and copies the numeric tier.

Borrowing through a pattern

summarise_one receives &Order. Matching it with a struct pattern automatically borrows the bound fields, so name is &String and id is &u32. This behavior is called match ergonomics; it predates Rust 2024. format! can display those references directly.

Moving a String out through a shared reference would be rejected. Destructuring an owned struct can move selected fields, but does not necessarily make all other fields unusable: unmoved fields can remain accessible. Here, matching by reference leaves the order available.

Use let for a pattern that always matches and match when selecting among alternatives. This exercise needs alternatives for order status and the special gold-pending case.

A passing submission must compile, pass the tests, and pass clippy -D warnings.

See the Rust pattern reference for struct, wildcard, rest, and reference binding rules.