Discriminants
A discriminant is the integer value or opaque identifier that distinguishes one variant of an enum from another. In Rust, every enum variant has an associated discriminant used by pattern matching (match, if let) to determine which variant is active at runtime.
Implicit and Explicit Discriminant Values
In a fieldless enum (an enum whose variants carry no data), the first variant defaults to discriminant 0, and each subsequent variant increments the previous discriminant by 1. Any variant can also specify an explicit integer constant with = value, and subsequent unspecified variants continue incrementing from that value.
#[derive(Debug, Clone, Copy)]
enum HttpStatus {
Ok = 200,
Created, // 201
Accepted, // 202
MovedPermanently = 301,
NotFound = 404,
}
fn main() {
println!("Ok: {}", HttpStatus::Ok as u16);
println!("Created: {}", HttpStatus::Created as u16);
println!("Accepted: {}", HttpStatus::Accepted as u16);
println!("MovedPermanently: {}", HttpStatus::MovedPermanently as u16);
println!("NotFound: {}", HttpStatus::NotFound as u16);
}
Ok: 200
Created: 201
Accepted: 202
MovedPermanently: 301
NotFound: 404
Duplicate discriminant values within the same enum are rejected at compile time with error E0081:
// ❌ does not compile
enum Duplicate {
First = 1,
Second = 1,
}
fn main() {}
error[E0081]: discriminant value `1` assigned more than once
--> src/main.rs:2:1
|
2 | enum Duplicate {
| ^^^^^^^^^^^^^^
3 | First = 1,
| - `1` assigned here
4 | Second = 1,
| - `1` assigned here
Explicit Discriminants on Data-Carrying Enums
An enum with data-carrying variants can only assign explicit integer discriminants if it has an explicit primitive integer representation attribute (#[repr(u8)], #[repr(i32)], etc.).
#[repr(u8)]
enum Frame {
Ping = 0x01,
Data(u32) = 0x02,
Close { reason: u16 } = 0xFF,
}
impl Frame {
fn tag(&self) -> u8 {
// SAFETY: Because `Frame` is `#[repr(u8)]`, its first byte is guaranteed
// to store the `u8` discriminant.
unsafe { *(self as *const Self as *const u8) }
}
}
fn main() {
let ping = Frame::Ping;
let data = Frame::Data(42);
let close = Frame::Close { reason: 1000 };
println!("ping tag: {:#04x}", ping.tag());
println!("data tag: {:#04x}", data.tag());
println!("close tag: {:#04x}", close.tag());
}
ping tag: 0x01
data tag: 0x02
close tag: 0xff
Comparing Variants with std::mem::discriminant
For enums that carry payload data (where variant as u8 is not allowed), std::mem::discriminant(&value) returns an opaque Discriminant<T> value that implements Eq, PartialEq, Hash, and Debug. It compares whether two enum instances belong to the same variant while ignoring their payload values.
use std::mem::discriminant;
enum Token {
Ident(String),
Number(i64),
Eof,
}
fn main() {
let a = Token::Ident(String::from("foo"));
let b = Token::Ident(String::from("bar"));
let c = Token::Number(42);
println!("a and b same variant: {}", discriminant(&a) == discriminant(&b));
println!("a and c same variant: {}", discriminant(&a) == discriminant(&c));
println!("discriminant(&c): {:?}", discriminant(&c));
}
a and b same variant: true
a and c same variant: false
discriminant(&c): Discriminant(1)