What Is the matches! Macro in Rust?

The `matches!` macro is a built-in Rust utility that checks if a value matches a specific pattern, returning a boolean result without requiring the verbose syntax of a full `match` expression.

When a match is too heavy

You're writing a function that checks if a configuration value is valid. The config is an enum with five variants. You only care if it's Production. You reach for match. You write the Production arm. The compiler rejects you with E0004 (non-exhaustive patterns). You have to add a catch-all arm. Now you have a four-line block just to return a boolean. It feels heavy. You want a single line that asks "Is this Production?" and gives you a yes or no.

That's the gap matches! fills. It's the idiomatic way to turn a pattern check into a boolean without the boilerplate of a full match expression.

The yes/no pattern gate

matches! is a macro that takes a value and a pattern. It returns true if the value fits the pattern, false otherwise. It discards any data inside the pattern. It's like holding a key up to a lock. You don't need to open the door or take the key apart. You just want to know if the key fits.

The macro is part of the standard library since Rust 1.42.0. It requires no external crates. The exclamation mark tells you it's a macro, not a function. This matters because patterns are compile-time syntax. You can't pass a pattern to a function. Macros let you embed the pattern directly in the call.

Minimal example

Here's the basic usage. You check an enum variant and an option without extracting anything.

enum Status {
    Active,
    Inactive,
    Pending { days: u32 },
}

fn main() {
    let status = Status::Active;
    let pending = Status::Pending { days: 5 };

    // matches! returns true if status is Active.
    // It returns false for Inactive or Pending.
    let is_active = matches!(status, Status::Active);
    assert!(is_active);

    // Check a struct variant with a wildcard.
    // The underscore ignores the days field.
    let is_pending = matches!(pending, Status::Pending { days: _ });
    assert!(is_pending);

    // Check a specific value inside the struct.
    // This returns false because days is 5, not 3.
    let is_pending_3 = matches!(pending, Status::Pending { days: 3 });
    assert!(!is_pending_3);
}

The macro handles the catch-all arm for you. You never get E0004 from matches!. The compiler knows the macro expands to a complete match expression.

How it expands

Under the hood, matches! is sugar. The compiler expands it into a match expression with two arms. Your pattern returns true. A wildcard _ returns false.

The expansion looks like this:

// matches!(expr, pattern) expands to:
match expr {
    pattern => true,
    _ => false,
}

This expansion happens at compile time. There is zero runtime cost compared to writing the match yourself. The macro saves you from typing the boilerplate and prevents you from forgetting the catch-all arm. It also ensures the pattern is checked for validity. If you write an impossible pattern, the compiler warns you just like it would in a regular match.

Guards and complex checks

matches! supports guards. You can add an if clause to the pattern. The guard runs only if the pattern matches. This lets you check conditions on the matched data without extracting it.

use std::io::{self, ErrorKind};

fn is_not_found(err: &io::Error) -> bool {
    // The guard checks the ErrorKind.
    // The pattern matches any Err, then the guard filters by kind.
    matches!(err.kind(), ErrorKind::NotFound)
}

fn main() {
    let result = std::fs::File::open("missing.txt");

    // Use a guard to check a specific error variant and its payload.
    // The guard accesses the error kind without binding the error to a variable.
    let is_missing = matches!(result, Err(ref e) if e.kind() == ErrorKind::NotFound);
    assert!(is_missing);
}

Guards make matches! powerful for complex checks. You can combine pattern structure with runtime logic. The guard expression has access to bindings inside the pattern, but those bindings don't leak outside the macro.

Realistic usage

You'll see matches! in filtering, validation, and state checks. It shines when you pass a condition to a higher-order function.

#[derive(Debug)]
enum Event {
    Click { x: i32, y: i32 },
    Key { code: u32 },
    Scroll { delta: f64 },
}

fn main() {
    let events = vec![
        Event::Click { x: 10, y: 20 },
        Event::Key { code: 65 },
        Event::Scroll { delta: 1.5 },
        Event::Click { x: 50, y: 50 },
    ];

    // Filter events to keep only clicks.
    // matches! provides a clean boolean predicate.
    let clicks: Vec<_> = events
        .into_iter()
        .filter(|e| matches!(e, Event::Click { .. }))
        .collect();

    assert_eq!(clicks.len(), 2);
}

Convention aside: For Option and Result, the community prefers is_some(), is_ok(), and their variants for simple existence checks. matches!(opt, Some(_)) works, but opt.is_some() is more readable. Use matches! when the pattern gets interesting, like checking a specific variant or using a guard.

Pitfalls and ownership

The biggest trap is expecting data extraction. matches! returns a boolean. Any bindings inside the pattern vanish immediately. If you write matches!(opt, Some(x)), the variable x does not exist after the macro call. You cannot use x to print the value. The compiler will reject any attempt to use x with an unresolved name error. Use if let when you need the data.

Another subtle issue is ownership. matches! takes ownership of the expression unless the pattern borrows. If you pass a variable, the macro moves it.

let opt = Some(42);

// This moves opt into the macro.
let has_value = matches!(opt, Some(_));

// Error: E0382 (use of moved value).
// println!("{:?}", opt); // This line would fail to compile.

If you need to use the value after the check, pass a reference. Match ergonomics usually handle the rest.

let opt = Some(42);

// Pass a reference. The pattern matches the inner value via ergonomics.
let has_value = matches!(&opt, Some(_));

// opt is still valid.
assert!(opt.is_some());

Treat matches! as a boolean gate. If you need the data, you're at the wrong door. Reach for if let or match when extraction matters.

Decision matrix

Use matches! when you need a boolean result from a pattern check and don't need to extract data. Use matches! when you want to write a concise guard condition in an if statement. Use matches! when you want to avoid the boilerplate of a two-arm match expression. Use matches! when you're filtering a collection and need a predicate based on structure.

Use if let when you need to extract values from a matching pattern and execute code with those values. Use if let when you only care about one specific variant and want to ignore the rest without a catch-all arm. Use if let when the logic inside the match is substantial and warrants a block.

Use match when you have multiple distinct branches that each require different logic or return different values. Use match when you need to handle every variant explicitly for exhaustiveness in a critical path. Use match when you're mapping one enum to another or computing a result based on the variant.

Use built-in methods like is_some() or is_ok() when checking Option or Result for presence. These methods are slightly more readable for simple existence checks and are the community convention for those types. Use matches! only when the pattern involves guards or complex structure that methods don't cover.

Where to go next