Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

An enum variant in Rust is a distinct, mutually exclusive state or constructor defined within an enumeration (enum). Each variant represents a specific memory layout that the enum can assume at runtime, allowing a single enum type to encapsulate heterogeneous data structures under a unified type signature.

Variant Structures

Rust supports three structural forms of enum variants, which mirror the three types of structs available in the language. A single enum can contain any combination of these variant types:
  1. Unit Variants: Contain no associated data. They act as simple flags or identifiers.
  2. Tuple Variants: Contain anonymous, positional data fields. Types are specified, but fields are unnamed.
  3. Struct Variants: Contain named data fields, defined using curly braces.
enum Message {
    Quit,                               // Unit variant
    Move(i32, i32),                     // Tuple variant
    Write { text: String, id: u32 },    // Struct variant
}

Instantiation Syntax

Variants are instantiated using the namespace of the parent enum, followed by the path separator (::), and the specific variant constructor.
// Instantiating a unit variant
let v_unit = Message::Quit;

// Instantiating a tuple variant
let v_tuple = Message::Move(10, 20);

// Instantiating a struct variant
let v_struct = Message::Write { 
    text: String::from("System fault"), 
    id: 101 
};

Data Extraction via Pattern Matching

Because the active variant of an enum is only known at runtime, accessing the data stored inside tuple and struct variants requires pattern matching. This is accomplished using match expressions or if let bindings, which destructure the variant and bind its inner data to local variables. To prevent consuming the enum instance and causing a borrow checker error, pattern matching is frequently performed on a reference to the enum.
let msg = Message::Write { text: String::from("Fault"), id: 101 };

// Using match on a reference to exhaustively handle all variants without moving data
match &msg {
    Message::Quit => {},
    Message::Move(x, y) => {
        // x and y are bound as references (&i32) to the tuple variant's inner values
    },
    Message::Write { text, id } => {
        // text (&String) and id (&u32) are bound to the struct variant's named fields
    },
}

// Using if let on a reference to destructure and extract data from a single variant
if let Message::Move(x, y) = &msg {
    // Executes only if msg is the Move variant
}

Discriminants

Under the hood, Rust differentiates between variants at runtime using a hidden integer tag known as a discriminant. For enums consisting exclusively of unit variants (C-like enums), you can explicitly assign integer values to the discriminants. If unassigned, Rust implicitly assigns discriminants starting at 0.
enum HttpError {
    BadRequest = 400,    // Explicit discriminant
    Unauthorized = 401,
    Forbidden = 403,
}
Explicit discriminants can also be assigned to enums containing tuple or struct variants. This requires the enum to have an explicit primitive representation attribute (e.g., #[repr(u8)]) to define the exact size and type of the discriminant in memory.
#[repr(u8)]
enum Token {
    Punctuation = 1,
    Number(f64) = 2,
    Identifier { name: String } = 3,
}

Memory Layout

Because an enum instance can only hold one variant at a time, the memory footprint of an enum is statically determined at compile time by the largest variant it contains. The total size of the enum is calculated as: Size of the largest variant + Size of the discriminant + Alignment padding
enum Data {
    Empty,          // 0 bytes data
    Small(u8),      // 1 byte data
    Large([u64; 4]) // 32 bytes data
}
// The size of `Data` will be at least 32 bytes (for `Large`) 
// + the size of the discriminant (e.g., 1 byte) 
// + padding to align to the 8-byte boundary of `u64`.
Rust’s compiler may optimize the discriminant away entirely in certain scenarios (known as niche optimization). For example, in Option<&T>, the None variant is represented by a null pointer, resulting in zero memory overhead for the enum wrapper.
Master Rust with Deep Grasping Methodology!Learn More