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 by1(0, 1, 2, ...). - You can assign an explicit integer constant to any variant using
Variant = Value, and any following unspecified variants continue incrementing by1from that value. - You can cast a fieldless enum value to an integer using
as(variant as u8orvariant 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