0
0
Rustprogramming~15 mins

Matching multiple patterns in Rust - Deep Dive

Choose your learning style9 modes available
Overview - Matching multiple patterns
What is it?
Matching multiple patterns in Rust means checking if a value fits any one of several possible shapes or values in a single match arm. Instead of writing separate arms for each pattern, you can combine them using the | symbol. This helps write cleaner and shorter code when you want to do the same thing for different cases. It is a way to compare a value against many options at once.
Why it matters
Without matching multiple patterns, programmers would write repetitive code for each case that behaves the same way. This makes code longer, harder to read, and more error-prone. Matching multiple patterns saves time and reduces mistakes by grouping similar cases together. It also makes programs easier to maintain and understand, especially when handling many possible inputs.
Where it fits
Before learning this, you should know basic Rust syntax and how the match statement works with single patterns. After this, you can learn about more advanced pattern matching features like destructuring, guards, and binding. Matching multiple patterns is a stepping stone to writing concise and expressive Rust code.
Mental Model
Core Idea
Matching multiple patterns lets you check if a value fits any one of several options in a single step, making code simpler and clearer.
Think of it like...
It's like choosing what to wear when the weather is either rainy or snowy — instead of checking each weather type separately, you say 'if it's rainy or snowy, wear boots' in one sentence.
┌─────────────────────────────┐
│          match value         │
├─────────────┬───────────────┤
│ Pattern A   │ Action A      │
├─────────────┼───────────────┤
│ Pattern B   │ Action B      │
├─────────────┼───────────────┤
│ Pattern C | Pattern D | ... │ Action C      │
└─────────────┴───────────────┘
Build-Up - 7 Steps
1
FoundationBasic match statement usage
🤔
Concept: Introduce how to use match with single patterns to decide actions based on a value.
In Rust, the match statement compares a value against patterns one by one. For example: let number = 2; match number { 1 => println!("One"), 2 => println!("Two"), 3 => println!("Three"), _ => println!("Other"), } This prints "Two" because number matches 2.
Result
Output: Two
Understanding the basic match structure is essential before combining patterns, as it shows how Rust checks values step-by-step.
2
FoundationPatterns and arms in match
🤔
Concept: Explain what patterns are and how each match arm has a pattern and an action.
A pattern is a shape or value Rust tries to match. Each arm in match has a pattern and code to run if matched. For example: match 'a' { 'a' => println!("Got a"), 'b' => println!("Got b"), _ => println!("Other"), } Here, 'a' and 'b' are patterns matching characters.
Result
Output: Got a
Knowing that patterns are like filters helps you see how matching multiple patterns combines these filters.
3
IntermediateCombining patterns with | operator
🤔Before reading on: do you think you can match multiple values in one arm by listing them separated by commas? Commit to your answer.
Concept: Introduce the | symbol to combine multiple patterns in one match arm.
Rust lets you write multiple patterns separated by | to match any of them in one arm. For example: let letter = 'c'; match letter { 'a' | 'e' | 'i' | 'o' | 'u' => println!("Vowel"), _ => println!("Consonant"), } This prints "Consonant" because 'c' is not a vowel.
Result
Output: Consonant
Using | reduces repetition and groups related cases, making code easier to read and maintain.
4
IntermediateMatching ranges and multiple patterns
🤔Before reading on: do you think you can combine ranges and single values with | in one match arm? Commit to your answer.
Concept: Show that you can mix ranges and single values with | to match multiple patterns.
You can combine ranges and single values using |. For example: let num = 7; match num { 1 | 3 | 5 | 7 | 9 => println!("Odd single-digit"), 10..=20 => println!("Between 10 and 20"), _ => println!("Other"), } This prints "Odd single-digit" because 7 matches one of the listed values.
Result
Output: Odd single-digit
Combining ranges and values with | gives flexible pattern matching for many cases in one arm.
5
IntermediateUsing multiple patterns with destructuring
🤔Before reading on: do you think you can use | with patterns that extract parts of data, like tuples? Commit to your answer.
Concept: Explain how to match multiple complex patterns that destructure data using |.
You can combine patterns that extract parts of data. For example: let point = (0, 5); match point { (0, y) | (y, 0) => println!("On an axis at {}", y), _ => println!("Elsewhere"), } This prints "On an axis at 5" because (0,5) matches (0, y).
Result
Output: On an axis at 5
Matching multiple destructured patterns lets you handle similar shapes with one action, saving code and clarifying intent.
6
AdvancedLimitations with guards and multiple patterns
🤔Before reading on: do you think you can use a single guard condition for all patterns combined with |? Commit to your answer.
Concept: Discuss how guards (extra if conditions) interact with multiple patterns and their limitations.
When using | to combine patterns, a guard applies to all patterns together. For example: let num = 4; match num { 1 | 2 | 3 if num % 2 == 0 => println!("Even small number"), _ => println!("Other"), } This will NOT match 2 because the guard applies only to 3. To apply guards properly, you must repeat patterns or use nested matches.
Result
Output: Other
Knowing guard limitations prevents bugs where some patterns unexpectedly skip the guard, ensuring correct matching logic.
7
ExpertCompiler optimizations and pattern matching order
🤔Before reading on: do you think the order of patterns with | affects performance or matching behavior? Commit to your answer.
Concept: Explain how Rust compiles multiple patterns and how order and complexity affect performance and exhaustiveness checks.
Rust compiles match arms into efficient code, often using jump tables or decision trees. When you combine patterns with |, the compiler checks them all together. The order of patterns in | does not affect matching behavior but can affect compile-time exhaustiveness checks. Complex patterns may cause the compiler to generate more code, impacting compile time and binary size. Understanding this helps write efficient and maintainable matches.
Result
No visible output, but better compiled code and warnings if patterns are incomplete.
Knowing compiler behavior helps write matches that are both correct and efficient, avoiding surprises in large codebases.
Under the Hood
Rust's match statement compiles into a decision tree or jump table that checks the input value against each pattern efficiently. When multiple patterns are combined with |, the compiler treats them as alternatives in the same branch, generating code that tests each pattern in order until one matches. This reduces duplicated code and improves runtime speed. Guards add conditional checks after pattern matching, but when combined with |, the guard applies to all patterns collectively, which can cause subtle behavior differences.
Why designed this way?
Rust's pattern matching was designed for safety, expressiveness, and performance. Combining multiple patterns with | avoids repetitive code and makes matches concise. The design balances ease of use with compiler optimizations. Alternatives like separate arms for each pattern would be verbose and error-prone. The guard limitation exists because applying different guards to individual patterns in one arm would complicate the syntax and compiler logic.
┌───────────────┐
│   match val   │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Check pattern1│
├───────────────┤
│ Check pattern2│
├───────────────┤
│ ...           │
├───────────────┤
│ If any match  │
│   run action  │
└───────────────┘
       │
       ▼
  (optional guard)
       │
       ▼
  Execute code or continue
Myth Busters - 4 Common Misconceptions
Quick: Does a guard apply separately to each pattern in a combined arm with |, or to all patterns together? Commit to your answer.
Common Belief:A guard applies individually to each pattern separated by | in a match arm.
Tap to reveal reality
Reality:A guard applies to the entire combined pattern as a whole, not to each pattern separately.
Why it matters:Misunderstanding this causes unexpected matches or misses, leading to bugs where some patterns bypass the guard.
Quick: If you list multiple patterns with |, does the order affect which pattern matches first? Commit to your answer.
Common Belief:The order of patterns separated by | affects which pattern matches first and thus the behavior.
Tap to reveal reality
Reality:All patterns combined with | are treated as alternatives in the same arm; order does not affect which matches first because they share the same action.
Why it matters:Thinking order matters here can lead to unnecessary code rearrangement and confusion about match behavior.
Quick: Can you use commas instead of | to separate multiple patterns in a match arm? Commit to your answer.
Common Belief:You can separate multiple patterns with commas in a single match arm.
Tap to reveal reality
Reality:Rust requires the | symbol to combine multiple patterns; commas separate different match arms.
Why it matters:Using commas instead of | causes syntax errors and confusion about match syntax.
Quick: Does combining multiple patterns with | always make the code faster? Commit to your answer.
Common Belief:Combining multiple patterns with | always improves runtime performance.
Tap to reveal reality
Reality:Combining patterns improves code clarity but does not always improve runtime speed; the compiler optimizes matches regardless of | usage.
Why it matters:Assuming | always improves speed can mislead optimization efforts and distract from writing clear code.
Expert Zone
1
When combining patterns with |, the compiler checks exhaustiveness across all combined patterns, which can cause unexpected warnings if some patterns overlap or are unreachable.
2
Using | with complex destructuring patterns can increase compile time and binary size due to duplicated code paths internally, even if the source looks concise.
3
Guards combined with | apply to all patterns together, so to have different guards per pattern, you must split them into separate arms or use nested matches.
When NOT to use
Avoid combining patterns with | when each pattern requires a different guard or action, as this can cause logic errors. Instead, write separate match arms or use if-let chains. Also, for very complex patterns, splitting arms improves readability and compiler diagnostics.
Production Patterns
In real-world Rust code, matching multiple patterns is common for handling enums with many variants that share behavior, like error handling or input parsing. It is also used in state machines to group states with similar transitions. Experts carefully balance combining patterns for clarity with splitting arms for precise control.
Connections
Logical OR in Boolean algebra
Matching multiple patterns with | is like the OR operation combining multiple conditions.
Understanding how | combines patterns helps grasp how logical OR works in programming and math, showing a shared principle of combining alternatives.
Switch-case statements in other languages
Rust's match with multiple patterns replaces multiple case labels in switch statements.
Knowing this connection helps learners coming from other languages see how Rust improves on switch by allowing pattern matching and combining cases cleanly.
Decision trees in machine learning
Pattern matching compiles into decision trees that efficiently route inputs to outcomes.
Recognizing this link shows how Rust's match is an example of a decision tree, a concept used widely in AI and data science for classification.
Common Pitfalls
#1Using commas instead of | to combine patterns.
Wrong approach:match x { 1, 2, 3 => println!("One, two, or three"), _ => println!("Other"), }
Correct approach:match x { 1 | 2 | 3 => println!("One, two, or three"), _ => println!("Other"), }
Root cause:Confusing syntax for separating match arms (commas) with combining patterns (|).
#2Expecting guards to apply to each pattern separately in a combined arm.
Wrong approach:match x { 1 | 2 if x % 2 == 0 => println!("Even 1 or 2"), _ => println!("Other"), }
Correct approach:match x { 1 if x % 2 == 0 => println!("Even 1"), 2 if x % 2 == 0 => println!("Even 2"), _ => println!("Other"), }
Root cause:Misunderstanding that guard applies to all patterns combined, not individually.
#3Combining patterns with | but needing different actions.
Wrong approach:match x { 1 | 2 => println!("One"), // but want different messages for 1 and 2 _ => println!("Other"), }
Correct approach:match x { 1 => println!("One"), 2 => println!("Two"), _ => println!("Other"), }
Root cause:Trying to combine patterns that require distinct handling into one arm.
Key Takeaways
Matching multiple patterns with | lets you group several cases into one match arm, making code shorter and clearer.
Guards apply to all combined patterns together, so use separate arms if you need different conditions per pattern.
The order of patterns combined with | does not affect which matches first since they share the same action.
Using | is a powerful tool but requires understanding its syntax and limitations to avoid subtle bugs.
Rust compiles match statements efficiently, and combining patterns helps write expressive and maintainable code.