Question

Compare Rust Enums Without Verbose Pattern Matching

rustenumspattern-matching

Question

How can I filter a Rust iterator by excluding items whose enum field is Unknown without writing a verbose match expression?

For example, this works:

items.filter(|my_struct| match my_struct.my_enum {
    MyEnum::Unknown => false,
    _ => true,
})

But I would prefer an expression such as:

items.filter(|my_struct| my_struct.my_enum != MyEnum::Unknown)

This produces the error:

binary operation `!=` cannot be applied to type `MyEnum`

What is the idiomatic Rust alternative, and how can an enum support != comparisons?

Short Answer

You will learn why Rust enums do not automatically support == and !=, how to enable those operators with PartialEq, and when the matches! macro is clearer for checking enum variants in iterator filters.

Concept

Rust only allows == and != for types that implement the PartialEq trait. An enum does not receive that implementation automatically, even when it looks easy to compare.

For an enum you own, the usual solution is to derive PartialEq:

#[derive(PartialEq)]
enum MyEnum {
    Unknown,
    Active,
    Disabled,
}

After that, variants can be compared directly:

if value.my_enum != MyEnum::Unknown {
    // The value is not unknown.
}

For the specific task of asking whether a value has a particular variant, Rust also provides matches!:

!matches!(value.my_enum, MyEnum::Unknown)

matches! evaluates to true when a pattern matches. The ! negates that result, so it means “the enum is not Unknown.”

Pattern matching remains important when you need to extract data from a variant or handle multiple cases differently. For a simple yes/no variant check, PartialEq or matches! is usually more concise.

Mental Model

Think of an enum as a labeled card. Each card has one label, such as Unknown, Active, or Disabled.

  • PartialEq gives Rust permission to compare two cards and ask, “Do these have the same label and contents?”
  • matches! asks, “Does this card have this particular label?”

Use comparison when comparing against a value. Use matches! when the important question is the shape or variant of the value.

Take Quiz

Syntax and Examples

To use == or !=, derive PartialEq on the enum:

#[derive(Debug, PartialEq)]
enum Status {
    Unknown,
    Ready,
    Failed,
}

struct Record {
    status: Status,
}

fn main() {
    let records = vec![
        Record { status: Status::Unknown },
        Record { status: Status::Ready },
        Record { status: Status::Failed },
    ];

    let known: Vec<&Record> = records
        .iter()
        .filter(|record| record.status != Status::Unknown)
        .collect();

    println!("{} known records", known.len());
}

iter() yields references to Record, so the closure parameter is effectively a &Record. The filter keeps records for which the condition is true.

The equivalent variant-checking form uses matches!:

let known: Vec<&Record> = records
    .iter()
    .filter(|record| !matches!(record.status, Status::Unknown))
    .collect();

When writing enum variants outside the enum's defining scope, qualify them as Status::Unknown. You can also import variants with use Status::*;, but qualification is often clearer.

Step by Step Execution

Consider this filter:

let known: Vec<&Record> = records
    .iter()
    .filter(|record| record.status != Status::Unknown)
    .collect();

Given these records:

let records = vec![
    Record { status: Status::Unknown },
    Record { status: Status::Ready },
    Record { status: Status::Failed },
];

Execution proceeds as follows:

  1. records.iter() visits each Record by reference. It does not consume the vector.
  2. For the first record, record.status != Status::Unknown is false, so filter discards it.
  3. For the second record, Status::Ready != Status::Unknown is true, so filter keeps it.
  4. For the third record, Status::Failed != Status::Unknown is true, so filter keeps it.
  5. collect() stores the kept references in known.

The final known vector contains the Ready and Failed records.

Real World Use Cases

Enum filtering is common when programs model states, outcomes, or categories.

  • API processing: Ignore results with a Pending or Unknown status before displaying completed data.
  • Validation: Keep only records whose state is not Invalid.
  • Job queues: Process jobs with Queued status while skipping Cancelled jobs.
  • Parsing: Filter out tokens such as Whitespace before later compiler stages.
  • Monitoring: Select events that are not Ignored before sending alerts.

For a simple unit-like variant such as Unknown, both != and !matches! express this intent clearly.

Take Quiz

Real Codebase Usage

In production Rust code, choose the clearest form for the job.

Derive equality for ordinary comparisons

#[derive(Debug, PartialEq, Eq)]
enum JobState {
    Queued,
    Running,
    Completed,
    Cancelled,
}

fn can_start(state: JobState) -> bool {
    state == JobState::Queued
}

Eq is commonly derived together with PartialEq when every value can be compared for full equality. For ordinary enums whose fields also implement Eq, this is usually appropriate.

Use a guard clause for an excluded state

fn process(job: &Job) {
    if job.state == JobState::Cancelled {
        return;
    }

    // Continue processing a non-cancelled job.
}

Use matches! for grouped variants

let should_retry = matches!(result, JobResult::TemporaryError(_) | JobResult::TimedOut);

This is where matches! is particularly useful: a comparison cannot naturally express “one of these variants,” while a pattern can.

Use filter for selection, then map for transformation

let active_names: Vec<&str> = users
    .iter()
    .filter(|user| user.status == Status::Active)
    .map(|user| user.name.as_str())
    .collect();

This separates the selection rule from the transformation that follows it.

Common Mistakes

Forgetting PartialEq

This causes the error in the question:

// Missing #[derive(PartialEq)]
enum MyEnum {
    Unknown,
    Active,
}

// Error: `!=` is unavailable.

Fix it by deriving the trait:

#[derive(PartialEq)]
enum MyEnum {
    Unknown,
    Active,
}

Using an unqualified variant name

This may fail if Unknown is not in scope:

record.status != Unknown

Prefer the explicit form:

record.status != Status::Unknown

Returning the wrong boolean from filter

filter keeps values when its predicate returns true.

// This keeps only Unknown records, not known ones.
.filter(|record| record.status == Status::Unknown)

To exclude Unknown, use != or negate matches!.

Moving data out of a borrowed item

If an enum contains non-Copy data and you try to bind that data while iterating by reference, Rust may reject a move:

#[derive(PartialEq)]
enum Response {
    Error(String),
    Ok,
}

// `message` would attempt to move a String from a borrowed response.
// .filter(|response| matches!(response.kind, Response::Error(message)))

When only checking the variant, use _ instead:

.filter(|response| matches!(response.kind, Response::Error(_)))

If you need the inner data, match on a reference, such as match &response.kind.

Using match when a boolean expression is enough

This is valid but unnecessarily long for a simple exclusion:

.filter(|record| match record.status {
    Status::Unknown => false,
    _ => true,
})

Prefer:

.filter(|record| record.status != Status::Unknown)

or:

.filter(|record| !matches!(record.status, Status::Unknown))

Comparisons

ApproachBest forExample
== / != with PartialEqComparing to one exact enum valuestate != State::Unknown
matches!Checking one variant, patterns, or several variants`matches!(state, State::Ready
matchDifferent behavior or returned values for each casematch state { ... }
if letHandling one variant and ignoring the restif let State::Failed(reason) = state { ... }

PartialEq comparisons are concise for exact equality. matches! becomes more expressive when variants carry data or multiple patterns should count as a match. A full match is best when every branch needs distinct logic.

Take Quiz

Cheat Sheet

// Enable == and != for an enum you own.
#[derive(PartialEq)]
enum Status {
    Unknown,
    Active,
}

let is_active = status == Status::Active;
let is_known = status != Status::Unknown;

// Test whether a value has a variant.
let is_unknown = matches!(status, Status::Unknown);
let is_known = !matches!(status, Status::Unknown);

// Match multiple variants.
let can_run = matches!(status, Status::Active | Status::Queued);

// Filter an iterator.
let known: Vec<&Record> = records
    .iter()
    .filter(|record| record.status != Status::Unknown)
    .collect();
  • filter retains values only when its closure returns true.
  • Use EnumName::Variant unless the variant was explicitly imported.
  • Derive Eq too when all enum fields support full equality:
#[derive(PartialEq, Eq)]

FAQ

Why does my Rust enum not support !=?

The != operator requires the PartialEq trait. Add #[derive(PartialEq)] to an enum you own, provided all of its fields can also be compared.

Should I use matches! or == for an enum variant?

Use == when equality is the idea you want to communicate. Use matches! when checking patterns, matching several variants, or ignoring data inside a variant.

Does matches! return a boolean?

Yes. matches!(value, pattern) returns true if value matches pattern; otherwise it returns false.

How do I exclude one enum variant in Iterator::filter?

Use either of these forms:

.filter(|item| item.status != Status::Unknown)
.filter(|item| !matches!(item.status, Status::Unknown))

Why should I write Status::Unknown instead of Unknown?

Enum variants are namespaced under their enum type. Status::Unknown works without importing the variant and makes the code's meaning clearer.

Can I derive PartialEq for an enum with data?

Yes, if every field in every variant implements PartialEq.

#[derive(PartialEq)]
enum ResultValue {
    Number(i32),
    Message(String),
}

When is a full match still necessary?

Use match when each variant needs different code, when you need to extract associated data, or when Rust should verify that every possible variant is handled.

Take Quiz

Mini Project

Description

Build a small record filter that removes entries with an unknown status. This mirrors common application code where incomplete or unclassified data should not appear in a report.

Goal

Filter a list of device readings so that only readings with a known status are displayed.

Requirements

Define a ReadingStatus enum with Unknown, Normal, and Warning variants.

Create a Reading struct containing a device name and status.

Create several readings, including at least one with Unknown status.

Use an iterator filter to exclude unknown readings.

Print the remaining device names and statuses.

Take Quiz

Keep learning

Related questions

View more