Enums and Pattern Matching

Unit Variants

A unit variant (or fieldless variant) is an enum variant that carries no associated data payload—written simply as an identifier (VariantName) without parentheses (...) or curly braces { ... }.

Declaring and Using Unit Variants

When all variants of an enum are unit variants, the enum is called a fieldless enum (or C-like enum), though unit variants can also be mixed freely alongside tuple and struct variants in the same enum:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Direction {
    North,
    South,
    East,
    West,
}

impl Direction {
    fn opposite(self) -> Self {
        match self {
            Self::North => Self::South,
            Self::South => Self::North,
            Self::East => Self::West,
            Self::West => Self::East,
        }
    }
}

fn main() {
    let heading = Direction::North;
    let reverse = heading.opposite();

    println!("Heading: {heading:?}, Opposite: {reverse:?}");
}
Heading: North, Opposite: South

Compact 1-Byte Memory Representation

Because unit variants carry no payload fields, the compiler only needs to store the variant's integer tag (discriminant) in memory. By default, a fieldless enum with up to 256 variants occupies just 1 byte (u8) on the stack:

Memory Layout of `Direction` (4 unit variants -> 1 byte total!):
  Direction::North  -->  0x00 (1 byte)
  Direction::South  -->  0x01 (1 byte)
  Direction::East   -->  0x02 (1 byte)
  Direction::West   -->  0x03 (1 byte)
  (Unused values 0x04..=0xFF allow `Option<Direction>` to ALSO fit in 1 byte!)
use std::mem::size_of;

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

fn main() {
    println!("size_of::<Direction>()         = {} byte", size_of::<Direction>());
    println!("size_of::<Option<Direction>>() = {} byte", size_of::<Option<Direction>>());
}
size_of::<Direction>()         = 1 byte
size_of::<Option<Direction>>() = 1 byte

Implicit and Explicit Integer Discriminants

In a fieldless enum (an enum containing only unit variants), each variant has an associated integer discriminant:

  • If you do not specify a discriminant, the first variant starts at 0, and each subsequent variant increments by 1 (0, 1, 2, ...).
  • You can assign an explicit integer constant to any variant using Variant = Value, and any following unspecified variants continue incrementing by 1 from that value.
  • You can cast a fieldless enum value to an integer using as (variant as u8 or variant as i32):
#[derive(Debug, Clone, Copy)]
enum Priority {
    Low = 1,
    Medium,      // Automatically assigned 1 + 1 = 2
    High = 10,
    Critical,    // Automatically assigned 10 + 1 = 11
}

fn main() {
    for p in [Priority::Low, Priority::Medium, Priority::High, Priority::Critical] {
        println!("{p:<8?} -> discriminant = {}", p as u8);
    }
}
Low      -> discriminant = 1
Medium   -> discriminant = 2
High     -> discriminant = 10
Critical -> discriminant = 11

Controlling Exact Integer Layout with #[repr(...)]

When passing a fieldless enum across a network protocol or C FFI boundary, add a #[repr(u8)], #[repr(u16)], #[repr(i32)], or #[repr(C)] attribute to guarantee the exact integer width used to store the discriminant in memory.

Why Unit Variants Beat Boolean Parameters at Call Sites

Passing two or three bool arguments to a function (render_window(true, false)) makes call sites unreadable because the reader cannot tell what true and false mean without looking up the function definition.

Replacing boolean flags with two-variant unit enums makes every call site self-documenting and prevents swapping arguments by mistake:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BorderStyle {
    Bordered,
    Borderless,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Visibility {
    Visible,
    Hidden,
}

fn configure_panel(border: BorderStyle, visibility: Visibility) {
    println!("Panel configured: {border:?}, {visibility:?}");
}

fn main() {
    // Self-documenting call site (and swapping the two arguments fails to compile!):
    configure_panel(BorderStyle::Bordered, Visibility::Visible);
}
Panel configured: Bordered, Visible