---
title: How to Use the match Expression with Enums
summary: Use the `match` keyword to handle every variant of an enum explicitly, ensuring exhaustive and safe control flow.
lang: en
slug: how-to-use-the-match-expression-with-enums
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-the-match-expression-with-enums/
---

# How to Use the match Expression with Enums



## The vending machine that checks its wiring

You are building a simple game loop. Every frame, the engine produces an event: a key press, a mouse movement, or a quit signal. Your code needs to react differently to each one. In Python, you might write a chain of `if isinstance` checks or a `match` statement. In JavaScript, you'd check a type property or use a switch. In Rust, you define the event as an enum and use `match` to handle it.

The difference is not syntax. The difference is safety. Rust's `match` forces you to handle every possible variant of the enum. If you add a new event type later, the compiler refuses to build your code until you update every `match` that touches that enum. This prevents bugs where new states are silently ignored. You cannot forget a case. The compiler guarantees you handled everything.

## Pattern matching in plain words

Think of `match` like a vending machine with labeled slots. Each slot corresponds to a specific variant of your enum. When you feed a value into the machine, it checks the label. If the value matches a slot, that slot activates and runs its code. If you leave a slot empty, the machine won't work. Rust enforces this: you must wire up every slot, or the code won't compile. This is called exhaustive matching.

Enums in Rust are more than lists of names. They can carry data. A `KeyPressed` variant might hold the character and a shift flag. A `MouseMoved` variant might hold coordinates. `match` can extract that data automatically. When a pattern matches, the data inside the variant becomes available as local variables in that arm. You don't need to call getters or check for null. The structure of the pattern mirrors the structure of the data.

## Minimal example

Start with a simple enum and a `match` that handles every variant. The code below defines a `Direction` enum and matches a value to produce a string.

```rust
enum Direction {
    North,
    South,
    East,
    West,
}

/// Converts a Direction enum to a string description.
fn describe_direction(dir: Direction) -> &'static str {
    // match evaluates the value against patterns top to bottom.
    // Every variant must be covered, or the compiler rejects the code.
    // The result of the matching arm becomes the result of the match.
    match dir {
        Direction::North => "Up",
        Direction::South => "Down",
        Direction::East => "Right",
        Direction::West => "Left",
    }
}

fn main() {
    let dir = Direction::North;
    println!("Result: {}", describe_direction(dir));
}
```

The `match` expression takes `dir` and compares it against each arm. The first arm checks for `Direction::North`. Since `dir` is `North`, that arm matches and returns `"Up"`. The other arms are skipped. If `dir` were `South`, the first arm would fail, the second would match, and `"Down"` would be returned.

The compiler checks exhaustiveness. If you remove the `West` arm, the compiler produces error E0004 (non-exhaustive patterns). It tells you exactly which variant is missing. This check happens at compile time. There is no runtime overhead for exhaustiveness. The compiler generates a jump table or a series of comparisons based on the enum's discriminant.

Write the match. Let the compiler verify the logic. If the pattern doesn't cover all cases, the code is incomplete.

## Extracting data with destructuring

Enums often hold data. `match` can destructure the variant and bind the inner data to variables. This works for tuple variants and struct variants.

```rust
enum Event {
    KeyPressed { key: char, shift: bool },
    MouseMoved { x: i32, y: i32 },
    Quit,
}

/// Handles an event by printing a description.
fn handle_event(event: Event) {
    match event {
        // Destructure the struct-like variant.
        // `key` and `shift` become local variables in this arm.
        // The fields are moved out of the enum.
        Event::KeyPressed { key, shift } => {
            if shift {
                println!("Shift+{}", key);
            } else {
                println!("Pressed {}", key);
            }
        }
        // Destructure another struct variant.
        // `x` and `y` are bound to the coordinates.
        Event::MouseMoved { x, y } => println!("Mouse at ({}, {})", x, y),
        // Unit variant has no data.
        // Just run the code block.
        Event::Quit => println!("Shutting down"),
    }
}

fn main() {
    let e1 = Event::KeyPressed { key: 'a', shift: true };
    handle_event(e1);

    let e2 = Event::MouseMoved { x: 100, y: 200 };
    handle_event(e2);
}
```

When `event` is `KeyPressed`, the pattern `Event::KeyPressed { key, shift }` matches. The `key` and `shift` fields are moved out of the enum and bound to variables named `key` and `shift`. You can use these variables inside the arm. The names in the pattern must match the field names in the enum definition.

By default, `match` moves the value. If the enum contains types that don't implement `Copy`, the value is consumed. After the match, the original variable is no longer valid. This is usually what you want. If you need to keep the value, borrow it in the match.

Convention aside: When destructuring, Rust allows you to rename bindings. `Event::KeyPressed { key: k, shift: s }` binds `key` to `k` and `shift` to `s`. Use this when you need a shorter name or want to avoid shadowing. The community often prefers the concise form `{ key, shift }` when names match, as it reduces noise.

Trust the borrow checker. It usually has a point. If you try to use the value after a match that moves it, the compiler will stop you.

## Guards and the @ operator

Sometimes a pattern matches, but you need an extra condition. Guards let you add a boolean expression to an arm. The arm only runs if the pattern matches and the guard evaluates to true.

```rust
enum Number {
    Value(i32),
    Zero,
}

/// Classifies a number variant with a guard.
fn classify(num: Number) -> &'static str {
    match num {
        // The guard `if n > 0` runs only if the pattern matches.
        // `n` is bound to the inner value.
        Number::Value(n) if n > 0 => "Positive",
        Number::Value(n) if n < 0 => "Negative",
        // This arm catches Value(0) because the guards failed.
        Number::Value(_) => "Zero value",
        Number::Zero => "Explicit zero",
    }
}
```

Guards are evaluated after the pattern matches. They can access variables bound in the pattern. Guards do not affect exhaustiveness. The compiler still requires you to handle all variants. If a guard filters out some values, you must handle the remaining values in other arms.

The `@` operator lets you bind a value to a variable while still matching a pattern. This is useful when you need the whole value and a part of it.

```rust
enum Message {
    Text(String),
    Error(String),
}

/// Logs a message, capturing the full variant and the content.
fn log_message(msg: Message) {
    match msg {
        // `content` binds to the String inside the variant.
        // `full` binds to the entire Message variant.
        // This lets you use both the part and the whole.
        Message::Text(content) @ Message::Text(_) => {
            println!("Text message: {}", content);
            println!("Full variant: {:?}", full);
        }
        Message::Error(content) @ Message::Error(_) => {
            println!("Error: {}", content);
            println!("Full variant: {:?}", full);
        }
    }
}
```

The `@` syntax places the binding before the pattern. `content @ Message::Text(_)` binds `content` to the inner string and checks that the variant is `Text`. The wildcard `_` in the pattern ensures the match succeeds for any `Text` variant. Use `@` when you need to pass the full value to a function while also using the extracted data.

Guards add flexibility without breaking exhaustiveness. Use them when logic depends on values inside the variant.

## Pitfalls and compiler errors

Missing arms trigger E0004 (non-exhaustive patterns). This is the most common error when learning `match`. If you add a new variant to an enum, every `match` on that enum must be updated. The compiler lists the missing variants. Fix the error by adding arms or using a wildcard.

Using a wildcard `_` catches all remaining variants. This satisfies exhaustiveness. Use `_` when you truly don't care about the rest.

```rust
match value {
    ImportantCase => do_thing(),
    _ => {} // Ignore everything else.
}
```

Convention aside: An empty block `{}` in a match arm is allowed but often signals a missing implementation. The community prefers `_ => {}` with a comment explaining why the cases are ignored, or `_ => unreachable!()` if the cases are impossible. `unreachable!()` tells the compiler and readers that the code should never run there. If it does, the program panics. This is safer than silently ignoring data.

Borrowing issues arise when you match on a reference. If you have `&Option<T>`, the match binds references to the inner data.

```rust
let opt = Some(5);
match &opt {
    Some(x) => println!("{}", x), // x is &i32
    None => println!("None"),
}
```

Here, `x` is `&i32`, not `i32`. The pattern matches the reference, so the binding is also a reference. This prevents moving data out of a borrowed value. If you try to move a non-`Copy` type out of a reference, the compiler rejects it with E0507 (cannot move out of borrowed content). Fix this by dereferencing or by matching on the owned value.

Shadowing can cause confusion. If you bind a variable in a match arm, it shadows any outer variable with the same name. The shadowing is limited to the arm. This is usually fine, but be careful when the name hides a value you need later.

The compiler is your safety net. Don't cut the holes. If you can't handle the case, the code shouldn't run.

## Decision matrix

Use `match` when you need to handle every variant of an enum or pattern. Use `match` when the logic differs significantly between variants and you want the compiler to verify completeness. Use `match` when you need to return a value from the expression based on the variant. Use `match` when you are destructuring complex nested structures and need to extract multiple fields.

Use `if let` when you only care about one specific variant and want to ignore the rest. Use `if let` for simple checks where writing a full `match` adds noise. Use `if let` when the fallback case is trivial, like doing nothing or returning early.

Use `while let` when you are iterating over a sequence that returns `Option` or `Result` and want to loop until the value is exhausted. Use `while let` for consuming iterators or draining collections without writing an explicit loop condition.

Use guards when you need to filter variants based on their inner data. Use guards when the condition is simple and doesn't warrant a separate function. Use the `@` operator when you need both the extracted data and the full value.

Pick the tool that matches your intent. `match` is the general solution. `if let` is the shortcut. `while let` is the loop. The compiler helps you choose by rejecting incomplete patterns.

## Where to go next

- [What Is Exhaustive Pattern Matching in Rust?](/en/what-is-exhaustive-pattern-matching-in-rust/)
- [How to Use while let with Enums in Rust](/en/how-to-use-while-let-with-enums-in-rust/)
- [What Is the Difference Between Enums in Rust vs C/C++?](/en/what-is-the-difference-between-enums-in-rust-vs-cc/)