Bird
Raised Fist0
Rustprogramming~15 mins

Match guards in Rust - Deep Dive

Choose your learning style10 modes available
Overview - Match guards
What is it?
Match guards are extra conditions added to Rust's match arms that let you check more than just the pattern. They are like if statements attached to each pattern to decide if that arm should run. This helps you write more precise and clear code when matching values. Match guards make your matches smarter by adding extra checks.
Why it matters
Without match guards, you would need to write nested if statements inside match arms or repeat patterns, making code longer and harder to read. Match guards let you combine pattern matching and conditions in one place, making your code cleaner and less error-prone. This improves how you handle different cases in your programs, especially when conditions depend on the matched data.
Where it fits
Before learning match guards, you should understand basic Rust pattern matching with the match keyword and how to write simple match arms. After mastering match guards, you can explore more advanced Rust features like if let, while let, and custom pattern matching with enums and structs.
Mental Model
Core Idea
Match guards add extra if-like conditions to match arms, letting you choose an arm only if both the pattern and the condition are true.
Think of it like...
Imagine sorting mail into bins by address (pattern). Match guards are like checking if the mail is urgent before putting it in a special bin, so you only sort mail that matches the address and is urgent.
Match value
  │
  ├─ Pattern 1 if condition 1 → execute arm 1
  ├─ Pattern 2 if condition 2 → execute arm 2
  └─ _ (catch-all) → execute default arm
Build-Up - 7 Steps
1
FoundationBasic pattern matching in Rust
🤔
Concept: Introduce how Rust matches values against patterns using the match keyword.
let number = 3; match number { 1 => println!("One"), 2 => println!("Two"), 3 => println!("Three"), _ => println!("Other"), }
Result
Three
Understanding simple pattern matching is essential because match guards build on this by adding conditions to these patterns.
2
FoundationWhy simple patterns can be limiting
🤔
Concept: Show that sometimes matching only by pattern is not enough to express all conditions.
let number = 5; match number { 1 => println!("One"), 2 => println!("Two"), 3 => println!("Three"), _ => println!("Other"), } // But what if we want to check if number is odd or even?
Result
Other
Recognizing this limitation motivates the need for match guards to add extra checks beyond just the pattern.
3
IntermediateAdding match guards with if conditions
🤔Before reading on: do you think match guards can check multiple conditions or just one? Commit to your answer.
Concept: Introduce the syntax of match guards using if after the pattern to add conditions.
let number = 5; match number { n if n % 2 == 0 => println!("Even number: {}", n), n if n % 2 != 0 => println!("Odd number: {}", n), _ => println!("Unknown"), }
Result
Odd number: 5
Knowing that match guards let you add any condition after a pattern makes matches more flexible and expressive.
4
IntermediateUsing match guards with enums
🤔Before reading on: can match guards access data inside enum variants? Commit to your answer.
Concept: Show how match guards work with enum variants and their data fields.
enum Message { Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } let msg = Message::Move { x: 10, y: 5 }; match msg { Message::Move { x, y } if x == y => println!("Moving diagonally"), Message::Move { x, y } => println!("Moving to x: {}, y: {}", x, y), _ => println!("Other message"), }
Result
Moving to x: 10, y: 5
Understanding that match guards can use variables extracted from patterns allows you to write very precise conditional logic.
5
IntermediateCombining multiple conditions in guards
🤔Before reading on: do you think you can combine multiple conditions with && or || in match guards? Commit to your answer.
Concept: Explain how to use logical operators inside match guards to combine conditions.
let number = 8; match number { n if n > 0 && n % 2 == 0 => println!("Positive even number"), n if n > 0 && n % 2 != 0 => println!("Positive odd number"), _ => println!("Non-positive number"), }
Result
Positive even number
Knowing that match guards support full boolean logic lets you write complex conditions directly in the match.
6
AdvancedOrder and fall-through behavior with guards
🤔Before reading on: if multiple patterns match but only one guard is true, which arm runs? Commit to your answer.
Concept: Explain how match arms are checked in order and guards affect which arm runs.
let number = 4; match number { n if n % 2 == 0 => println!("Even number"), 4 => println!("Number four"), _ => println!("Other"), } // The first arm matches and guard is true, so second arm is ignored.
Result
Even number
Understanding that match arms are checked top to bottom and guards can prevent an arm from matching helps avoid subtle bugs.
7
ExpertPerformance and compiler optimizations with guards
🤔Before reading on: do you think match guards always compile to efficient code or can they cause runtime overhead? Commit to your answer.
Concept: Discuss how match guards affect Rust's pattern matching optimizations and possible runtime costs.
Match guards are evaluated as runtime if conditions after pattern matching. This means the compiler cannot always optimize matches into jump tables when guards are present. Guards add flexibility but may introduce small runtime overhead compared to pure pattern matching.
Result
Match guards trade some performance for expressiveness and safety.
Knowing the tradeoff between expressiveness and performance helps you decide when to use guards or refactor code for efficiency.
Under the Hood
When Rust runs a match with guards, it first tries to match the pattern. If the pattern matches, it then evaluates the guard condition as a boolean expression. Only if the guard returns true does Rust execute that arm. If the guard is false, Rust continues checking the next arms. This two-step check allows precise control but means guards run at runtime after pattern matching.
Why designed this way?
Rust's match is designed to be exhaustive and efficient. Guards were added to let programmers add extra checks without complicating the pattern syntax. This design keeps pattern matching simple and fast while allowing flexible conditions. Alternatives like nested ifs would make code messy and less readable.
┌───────────────┐
│ Match value   │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Pattern match │
└──────┬────────┘
       │ if matches
       ▼
┌───────────────┐
│ Evaluate guard│
└──────┬────────┘
       │ if true
       ▼
┌───────────────┐
│ Execute arm   │
└───────────────┘
       │ if false
       ▼
┌───────────────┐
│ Next arm      │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does a match guard change the pattern it is attached to? Commit yes or no.
Common Belief:A match guard changes the pattern to match only when the guard condition is true.
Tap to reveal reality
Reality:The pattern match happens first and is independent; the guard only adds an extra condition after the pattern matches.
Why it matters:Confusing guards with patterns can lead to writing incorrect patterns or expecting guards to filter patterns, causing bugs.
Quick: Can match guards cause the match to skip an arm even if the pattern matches? Commit yes or no.
Common Belief:If the pattern matches, the arm always runs regardless of the guard.
Tap to reveal reality
Reality:If the guard condition is false, Rust skips that arm and tries the next one.
Why it matters:Not knowing this can cause unexpected behavior when guards are false, leading to wrong arms running.
Quick: Are match guards evaluated at compile time? Commit yes or no.
Common Belief:Match guards are compile-time checks that optimize matching.
Tap to reveal reality
Reality:Match guards are evaluated at runtime as boolean expressions after pattern matching.
Why it matters:Assuming compile-time evaluation can mislead about performance and cause surprises in timing or side effects.
Quick: Can match guards use variables not introduced in the pattern? Commit yes or no.
Common Belief:Match guards can use any variables in scope, even if not from the pattern.
Tap to reveal reality
Reality:Match guards can only use variables introduced by the pattern or in the surrounding scope, but must be valid in that context.
Why it matters:Misusing variables in guards causes compilation errors and confusion about variable scope.
Expert Zone
1
Match guards prevent the compiler from using jump tables or other fast pattern matching optimizations, so use them judiciously in performance-critical code.
2
When multiple arms have overlapping patterns with guards, the order of arms affects which arm runs, so carefully order arms to avoid subtle bugs.
3
Match guards can capture variables from patterns and use them in complex boolean expressions, enabling very expressive conditional matching beyond simple patterns.
When NOT to use
Avoid match guards when performance is critical and patterns alone can express the logic. Instead, use nested if statements after matching or refactor code to simpler patterns. Also, avoid guards if they make the match arms too complex; consider splitting logic into functions.
Production Patterns
In real-world Rust code, match guards are often used to handle enums with data where conditions depend on fields, such as filtering messages or commands. They also appear in parsing code to validate input formats and in state machines to check state conditions concisely.
Connections
Conditional statements (if/else)
Match guards combine pattern matching with conditional checks, similar to if statements inside match arms.
Understanding match guards helps bridge the gap between simple pattern matching and full conditional logic, showing how Rust blends these concepts elegantly.
Functional programming pattern matching
Match guards extend traditional pattern matching by adding boolean conditions, a feature seen in some functional languages.
Knowing match guards deepens understanding of how Rust adapts functional patterns with extra flexibility for practical programming.
Filtering in database queries
Match guards act like filters applied after matching, similar to WHERE clauses in SQL that refine results after selecting rows.
Seeing match guards as filters clarifies their role in narrowing down matches, connecting programming patterns to data querying concepts.
Common Pitfalls
#1Writing guards that always evaluate to false, causing arms to never run.
Wrong approach:match x { n if n > 10 && n < 5 => println!("Impossible"), _ => println!("Other"), }
Correct approach:match x { n if n > 10 || n < 5 => println!("Possible"), _ => println!("Other"), }
Root cause:Misunderstanding boolean logic in guards leads to conditions that can never be true.
#2Using variables in guards not introduced by the pattern or in scope, causing compile errors.
Wrong approach:let y = 5; match x { n if n == y => println!("Equal"), _ => println!("Not equal"), }
Correct approach:let y = 5; match x { n if n == y => println!("Equal"), _ => println!("Not equal"), }
Root cause:Trying to use undefined or out-of-scope variables in guards causes errors.
#3Expecting match guards to change pattern matching behavior or to filter patterns themselves.
Wrong approach:match x { Some(n) if n > 0 => println!("Positive"), Some(_) => println!("Non-positive"), None => println!("None"), } // Assuming the first arm only matches Some(n) with n > 0 pattern-wise.
Correct approach:match x { Some(n) if n > 0 => println!("Positive"), Some(_) => println!("Non-positive"), None => println!("None"), } // Pattern matches Some(n) always; guard filters at runtime.
Root cause:Confusing pattern matching and guard evaluation leads to misunderstanding match flow.
Key Takeaways
Match guards let you add extra conditions to Rust match arms, combining pattern matching with boolean checks.
They are evaluated after the pattern matches, and if the guard is false, Rust tries the next arm.
Using guards makes your code more expressive and concise, especially when matching complex data like enums.
Guards can affect performance because they prevent some compiler optimizations, so use them wisely.
Understanding the order and scope of guards helps avoid subtle bugs and write clearer Rust code.

Practice

(1/5)
1.

What is the purpose of a match guard in Rust?

easy
A. To add an extra condition to a pattern in a match statement
B. To replace the entire match statement with an if statement
C. To declare a new variable inside a match arm
D. To loop over all patterns automatically

Solution

  1. Step 1: Understand match guards

    Match guards add extra conditions to patterns in a match, allowing more precise checks.
  2. Step 2: Compare options

    Only To add an extra condition to a pattern in a match statement correctly describes adding extra conditions to patterns.
  3. Final Answer:

    To add an extra condition to a pattern in a match statement -> Option A
  4. Quick Check:

    Match guard = extra condition [OK]
Hint: Match guards add conditions after patterns using if [OK]
Common Mistakes:
  • Thinking match guards replace match statements
  • Confusing match guards with variable declarations
  • Assuming match guards loop over patterns
2.

Which of the following is the correct syntax for a match guard in Rust?

match x {
    5 if x > 3 => println!("Greater than 3"),
    _ => println!("Other")
}
easy
A. match x { if 5 > 3 => println!("Greater than 3"), _ => println!("Other") }
B. match x { 5 => if x > 3 { println!("Greater than 3") }, _ => println!("Other") }
C. match x { 5 if x > 3 => println!("Greater than 3"), _ => println!("Other") }
D. match x { 5 => println!("Greater than 3") if x > 3, _ => println!("Other") }

Solution

  1. Step 1: Identify correct match guard syntax

    Match guards use pattern if condition => expression syntax.
  2. Step 2: Check options

    match x { 5 if x > 3 => println!("Greater than 3"), _ => println!("Other") } correctly places if x > 3 after the pattern 5.
  3. Final Answer:

    match x { 5 if x > 3 => println!("Greater than 3"), _ => println!("Other") } -> Option C
  4. Quick Check:

    Pattern if condition => action [OK]
Hint: Use 'pattern if condition => action' for match guards [OK]
Common Mistakes:
  • Placing if before the pattern
  • Using if inside the match arm block
  • Putting if after the expression
3.

What will be the output of the following Rust code?

let num = 7;
match num {
    n if n < 5 => println!("Less than 5"),
    n if n <= 10 => println!("Between 5 and 10"),
    _ => println!("Greater than 10"),
}
medium
A. Between 5 and 10
B. No output (compile error)
C. Greater than 10
D. Less than 5

Solution

  1. Step 1: Evaluate match arms in order

    num is 7, first guard checks if 7 < 5 (false), so skip.
  2. Step 2: Check second guard

    7 <= 10 is true, so this arm runs and prints "Between 5 and 10".
  3. Final Answer:

    Between 5 and 10 -> Option A
  4. Quick Check:

    7 ≤ 10 matches second guard [OK]
Hint: Match arms checked top to bottom; first true guard runs [OK]
Common Mistakes:
  • Assuming first guard matches 7
  • Thinking all guards run
  • Confusing output with compile error
4.

Identify the error in this Rust code using match guards:

let x = 10;
match x {
    n if n > 5 => println!("Greater than 5"),
    n if n > 8 => println!("Greater than 8"),
    _ => println!("Other"),
}
medium
A. Syntax error: match guards cannot use variables
B. The wildcard pattern must come before guarded patterns
C. Missing semicolon after println! macros
D. The second guard is unreachable because the first guard covers all cases greater than 5

Solution

  1. Step 1: Analyze guard order and coverage

    The first guard matches all n > 5, so it includes all n > 8 as well.
  2. Step 2: Identify unreachable code

    The second guard is never reached because the first guard catches those values first.
  3. Final Answer:

    The second guard is unreachable because the first guard covers all cases greater than 5 -> Option D
  4. Quick Check:

    First guard shadows second guard [OK]
Hint: Order guards from most specific to least specific [OK]
Common Mistakes:
  • Ignoring unreachable code warnings
  • Thinking guards can overlap safely
  • Misunderstanding guard evaluation order
5.

Given a list of integers, write a Rust match expression with match guards to print:

  • "Small" if the number is less than 10
  • "Medium" if the number is between 10 and 20 (inclusive)
  • "Large" if the number is greater than 20

Which of the following match arms correctly implements this logic for a variable n?

hard
A. match n { x if x <= 10 => println!("Small"), x if x > 10 && x < 20 => println!("Medium"), _ => println!("Large"), }
B. match n { x if x < 10 => println!("Small"), x if x >= 10 && x <= 20 => println!("Medium"), _ => println!("Large"), }
C. match n { x if x < 10 => println!("Small"), x if x > 20 => println!("Medium"), _ => println!("Large"), }
D. match n { x if x < 10 => println!("Small"), x if x > 10 && x < 20 => println!("Medium"), _ => println!("Large"), }

Solution

  1. Step 1: Check conditions for each size category

    "Small" is for n < 10, "Medium" for 10 ≤ n ≤ 20, "Large" for n > 20.
  2. Step 2: Verify each match arm

    match n { x if x < 10 => println!("Small"), x if x >= 10 && x <= 20 => println!("Medium"), _ => println!("Large"), } correctly uses x < 10, x >= 10 && x <= 20, and wildcard for others.
  3. Final Answer:

    match n { x if x < 10 => println!("Small"), x if x >= 10 && x <= 20 => println!("Medium"), _ => println!("Large"), } -> Option B
  4. Quick Check:

    Guards match exact ranges [OK]
Hint: Use inclusive ranges with && for medium category [OK]
Common Mistakes:
  • Using wrong comparison operators
  • Overlapping ranges causing unreachable arms
  • Missing inclusive bounds for medium range
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard