Memory Layout and Representation

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)