How does strum::VariantNames provide access to enum variant names as string slices at compile time?

strum::VariantNames is a derive macro that generates a static array of string slices containing the variant names of an enum, accessible through the VARIANTS constant at compile time without runtime allocation. The macro produces a const VARIANTS: &'static [&'static str] associated constant on the enum, where each string is the variant name exactly as written in the source code. This enables compile-time iteration over variant names, static validation of variant lookups, and zero-allocation name access—particularly useful for generating documentation, building CLIs with variant-based arguments, or implementing configuration parsers that map strings to enum values.

Basic VariantNames Usage

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Color {
    Red,
    Green,
    Blue,
}
 
fn basic_usage() {
    // VARIANTS is a static constant generated by the derive
    const VARIANTS: &[&str] = Color::VARIANTS;
    
    // Access variant names at compile time
    assert_eq!(Color::VARIANTS, ["Red", "Green", "Blue"]);
    
    // No runtime allocation - these are static string slices
    for name in Color::VARIANTS {
        println!("Variant: {}", name);
    }
}

The VariantNames derive generates a static array available at compile time.

The Generated Constant

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Status {
    Active,
    Inactive,
    Pending,
}
 
fn generated_code() {
    // The derive generates something equivalent to:
    // impl Status {
    //     pub const VARIANTS: &'static [&'static str] = &["Active", "Inactive", "Pending"];
    // }
    
    // It's a constant, not a function call
    let variants: &'static [&'static str] = Status::VARIANTS;
    
    // The strings live for the entire program lifetime
    // No heap allocation, no runtime cost
}

The generated VARIANTS constant contains static references to static strings.

Compile-Time Usage

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Direction {
    North,
    South,
    East,
    West,
}
 
const DIRECTION_COUNT: usize = Direction::VARIANTS.len();
 
fn compile_time_usage() {
    // Can use VARIANTS in const contexts
    const FIRST_DIRECTION: &str = Direction::VARIANTS[0];
    assert_eq!(FIRST_DIRECTION, "North");
    
    // Can use in array size expressions
    let direction_labels: [&str; DIRECTION_COUNT] = {
        let mut arr = [""; DIRECTION_COUNT];
        let mut i = 0;
        while i < DIRECTION_COUNT {
            arr[i] = Direction::VARIANTS[i];
            i += 1;
        }
        arr
    };
    
    // Static assertions are possible
    assert!(Direction::VARIANTS.len() == 4);
}

VARIANTS can be used in compile-time contexts including constants and array sizes.

Combining with Other Strum Traits

use strum::{VariantNames, EnumIter, IntoEnumIterator, EnumString, FromRepr};
 
#[derive(VariantNames, EnumIter, EnumString, Debug, Clone, Copy)]
enum Animal {
    Dog,
    Cat,
    Bird,
}
 
fn combined_traits() {
    // VariantNames gives string names
    assert_eq!(Animal::VARIANTS, ["Dog", "Cat", "Bird"]);
    
    // EnumIter gives actual values
    for animal in Animal::iter() {
        println!("{:?}", animal);
    }
    
    // EnumString converts strings to values
    use std::str::FromStr;
    let dog = Animal::from_str("Dog").unwrap();
    
    // Common pattern: use VARIANTS to validate input
    fn parse_animal(s: &str) -> Result<Animal, String> {
        if Animal::VARIANTS.contains(&s) {
            Animal::from_str(s).map_err(|e| format!("Parse error: {}", e))
        } else {
            Err(format!("Unknown animal: {}. Valid options: {:?}", s, Animal::VARIANTS))
        }
    }
}

VariantNames pairs naturally with other strum traits for complete enum handling.

Variant Names vs Display Representation

use strum::{VariantNames, Display};
 
#[derive(VariantNames, Display)]
enum Priority {
    High,
    Medium,
    Low,
}
 
fn names_vs_display() {
    // VARIANTS uses the exact source code names
    assert_eq!(Priority::VARIANTS, ["High", "Medium", "Low"]);
    
    // Display uses the display representation (same by default)
    let high = Priority::High;
    println!("Display: {}", high);  // "High"
    
    // With strum(to_string = "..."), Display differs:
    #[derive(VariantNames, Display)]
    enum Status {
        #[strum(to_string = "status:active")]
        Active,
        #[strum(to_string = "status:inactive")]
        Inactive,
    }
    
    // VARIANTS still uses source code names
    assert_eq!(Status::VARIANTS, ["Active", "Inactive"]);
    
    // Display uses the custom strings
    assert_eq!(format!("{}", Status::Active), "status:active");
}

VARIANTS always contains the exact source code names, not customized display strings.

Building CLI Argument Enums

use strum::VariantNames;
 
#[derive(VariantNames, Clone, Copy)]
enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}
 
fn cli_usage() {
    // Generate help text at compile time
    const LOG_LEVEL_HELP: &str = {
        // Note: This would need const concat for real usage
        "Log level options: Error, Warn, Info, Debug, Trace"
    };
    
    // Runtime validation
    fn validate_log_level(s: &str) -> Result<LogLevel, String> {
        if LogLevel::VARIANTS.contains(&s) {
            // Would also need FromStr derived
            todo!("Parse the valid level")
        } else {
            Err(format!(
                "Invalid log level '{}'. Valid options: {:?}",
                s, LogLevel::VARIANTS
            ))
        }
    }
    
    // Generate usage string dynamically
    let valid_levels = LogLevel::VARIANTS.join(", ");
    println!("Valid levels: {}", valid_levels);
}

VARIANTS enables building informative error messages and help text.

Static Lookup Tables

use strum::VariantNames;
 
#[derive(VariantNames, Clone, Copy, PartialEq, Eq)]
enum ConfigKey {
    Database,
    Cache,
    Timeout,
}
 
// Build static lookup tables using VARIANTS
const CONFIG_COUNT: usize = ConfigKey::VARIANTS.len();
 
fn static_tables() {
    // Create a static description map
    const DESCRIPTIONS: [&str; 3] = [
        "Database connection string",
        "Cache configuration",
        "Request timeout in seconds",
    ];
    
    // Use VARIANTS to build documentation
    fn print_config_docs() {
        for (i, name) in ConfigKey::VARIANTS.iter().enumerate() {
            println!("{}: {}", name, DESCRIPTIONS[i]);
        }
    }
    
    print_config_docs();
}

VARIANTS enables mapping variant names to associated static data.

Enum Size Calculation

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Color {
    Red,
    Green,
    Blue,
    Yellow,
    Cyan,
    Magenta,
}
 
fn enum_size() {
    // VARIANTS.len() is known at compile time
    const COLOR_COUNT: usize = Color::VARIANTS.len();
    
    // Use for array sizing
    let mut color_counts: [u32; COLOR_COUNT] = [0; COLOR_COUNT];
    
    // Use for indexed access
    fn color_index(color: &str) -> Option<usize> {
        Color::VARIANTS.iter().position(|&v| v == color)
    }
    
    assert_eq!(color_index("Green"), Some(1));
    assert_eq!(color_index("Purple"), None);
}

The length is known at compile time, enabling fixed-size arrays.

Serde Integration

use strum::VariantNames;
use serde::{Serialize, Deserialize};
 
#[derive(VariantNames, Serialize, Deserialize, Clone, Copy)]
enum Role {
    Admin,
    User,
    Guest,
}
 
fn serde_integration() {
    // VARIANTS is useful for custom serialization
    fn role_as_string(role: Role) -> &'static str {
        match role {
            Role::Admin => Role::VARIANTS[0],
            Role::User => Role::VARIANTS[1],
            Role::Guest => Role::VARIANTS[2],
        }
    }
    
    // More commonly: validate deserialization
    fn validate_role(s: &str) -> Result<Role, String> {
        if Role::VARIANTS.contains(&s) {
            // Would use Role::from_str() with proper derive
            Ok(match s {
                "Admin" => Role::Admin,
                "User" => Role::User,
                "Guest" => Role::Guest,
                _ => unreachable!(),
            })
        } else {
            Err(format!("Invalid role. Valid options: {:?}", Role::VARIANTS))
        }
    }
}

VARIANTS helps validate and document serialized values.

No Allocation Guarantee

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Size {
    Small,
    Medium,
    Large,
}
 
fn no_allocation() {
    // VARIANTS is &'static [&'static str]
    // No String allocation ever
    
    fn get_variants() -> &'static [&'static str] {
        // Direct return of static reference
        Size::VARIANTS
    }
    
    // Compare with EnumIter which creates an iterator
    use strum::IntoEnumIterator;
    #[derive(VariantNames, EnumIter)]
    enum SizeIter {
        Small,
        Medium,
        Large,
    }
    
    // VARIANTS: static, zero allocation
    let names: &'static [&'static str] = Size::VARIANTS;
    
    // iter(): creates iterator struct (stack allocation)
    // but each variant is still zero-cost
    for size in SizeIter::iter() {
        // size is SizeIter value
    }
}

VARIANTS provides string names without any heap allocation.

Handling Complex Variants

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
 
fn complex_variants() {
    // VARIANTS contains variant names, not full structure
    assert_eq!(Message::VARIANTS, ["Quit", "Move", "Write", "ChangeColor"]);
    
    // The struct/tuple fields are NOT included
    // Just the variant identifier name
    
    // This means VARIANTS is useful for variant identification
    // Not for full type introspection
}

VARIANTS only captures variant names, not the structure of variants with data.

Nested Enums

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Outer {
    Alpha,
    Beta,
}
 
#[derive(VariantNames)]
enum Inner {
    Gamma,
    Delta,
}
 
fn nested_enums() {
    // Each enum has its own VARIANTS
    assert_eq!(Outer::VARIANTS, ["Alpha", "Beta"]);
    assert_eq!(Inner::VARIANTS, ["Gamma", "Delta"]);
    
    // No inheritance or nesting relationship
    // VARIANTS is per-enum
}

Each enum derives its own VARIANTS constant independently.

Customization with Strum Attributes

use strum::VariantNames;
 
#[derive(VariantNames)]
enum HttpMethod {
    #[strum(to_string = "GET")]
    Get,
    #[strum(to_string = "POST")]
    Post,
    #[strum(to_string = "PUT")]
    Put,
}
 
fn custom_attributes() {
    // Note: VariantNames uses source code names
    // The to_string attribute affects Display, not VARIANTS
    assert_eq!(HttpMethod::VARIANTS, ["Get", "Post", "Put"]);
    
    // VARIANTS is always the actual variant names
    // Use Display (with to_string) for custom strings
    
    use strum::Display;
    #[derive(VariantNames, Display)]
    enum HttpMethodDisplay {
        #[strum(to_string = "GET")]
        Get,
        #[strum(to_string = "POST")]
        Post,
    }
    
    // VARIANTS: source names
    assert_eq!(HttpMethodDisplay::VARIANTS, ["Get", "Post"]);
    
    // Display: custom strings
    // assert_eq!(format!("{}", HttpMethodDisplay::Get), "GET");
}

VariantNames uses source code names; to_string attributes affect Display, not VARIANTS.

Serialization Format Generation

use strum::VariantNames;
use std::fmt;
 
#[derive(VariantNames, Clone, Copy)]
enum Status {
    Pending,
    Processing,
    Completed,
    Failed,
}
 
fn generate_docs() {
    // Generate JSON Schema style documentation
    let schema = generate_enum_schema("Status", Status::VARIANTS);
    println!("{}", schema);
}
 
fn generate_enum_schema(name: &str, variants: &[&str]) -> String {
    let variant_list = variants
        .iter()
        .map(|v| format!("\"{}\"", v))
        .collect::<Vec<_>>()
        .join(", ");
    
    format!(
        r#"{{
  "type": "string",
  "enum": [{}]
}}"#,
        variant_list
    )
}

VARIANTS enables automatic schema and documentation generation.

Comparison with EnumIter

use strum::{VariantNames, EnumIter, IntoEnumIterator, Display};
 
#[derive(VariantNames, EnumIter, Display)]
enum Fruit {
    Apple,
    Banana,
    Cherry,
}
 
fn variantnames_vs_iter() {
    // VariantNames: static string names
    // - Zero allocation
    // - Compile-time access
    // - String slices only
    // - No variant values
    
    for name in Fruit::VARIANTS {
        println!("Name: {}", name);  // &str
    }
    
    // EnumIter: actual variant values
    // - Creates iterator struct
    // - Runtime iteration
    // - Full variant values
    // - Can use all variant data
    
    for fruit in Fruit::iter() {
        println!("Variant: {:?}", fruit);  // Fruit value
        println!("Display: {}", fruit);    // Uses Display trait
    }
    
    // Key difference:
    // - VARIANTS: ["Apple", "Banana", "Cherry"]
    // - iter(): [Fruit::Apple, Fruit::Banana, Fruit::Cherry]
}

VariantNames provides names; EnumIter provides actual values.

Performance Characteristics

use strum::VariantNames;
 
#[derive(VariantNames)]
enum LargeEnum {
    Variant0,
    Variant1,
    Variant2,
    // ... potentially hundreds of variants
}
 
fn performance() {
    // VARIANTS access: O(1) - just a slice lookup
    let first = LargeEnum::VARIANTS[0];
    
    // VARIANTS.len(): O(1) - stored in slice metadata
    let count = LargeEnum::VARIANTS.len();
    
    // Linear search: O(n)
    let found = LargeEnum::VARIANTS.iter().position(|&v| v == "Variant1");
    
    // No heap allocation at any point
    // No reference counting
    // Strings are 'static
    
    // Memory layout:
    // VARIANTS is a pointer to a static array of pointers to static strings
    // Total: 1 pointer + N pointers + string data (all in read-only memory)
}

All VARIANTS operations are zero-allocation and extremely fast.

Real-World Example: Configuration Parser

use strum::VariantNames;
use std::str::FromStr;
 
#[derive(VariantNames, Clone, Copy, PartialEq, Eq)]
enum OutputFormat {
    Json,
    Yaml,
    Toml,
    Xml,
}
 
impl FromStr for OutputFormat {
    type Err = String;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Json" | "json" => Ok(OutputFormat::Json),
            "Yaml" | "yaml" => Ok(OutputFormat::Yaml),
            "Toml" | "toml" => Ok(OutputFormat::Toml),
            "Xml" | "xml" => Ok(OutputFormat::Xml),
            _ => Err(format!(
                "Unknown format '{}'. Valid formats: {:?}",
                s, OutputFormat::VARIANTS
            )),
        }
    }
}
 
fn parse_config_example() {
    fn parse_format(input: &str) -> Result<OutputFormat, String> {
        // Use VARIANTS for case-insensitive check
        let input_lower = input.to_lowercase();
        
        for variant in OutputFormat::VARIANTS {
            if variant.to_lowercase() == input_lower {
                return OutputFormat::from_str(input);
            }
        }
        
        Err(format!(
            "Invalid format '{}'. Valid options: {}",
            input,
            OutputFormat::VARIANTS.join(", ")
        ))
    }
    
    // Usage
    assert!(parse_format("json").is_ok());
    assert!(parse_format("invalid").is_err());
}

VARIANTS enables informative error messages and validation.

Synthesis

Quick reference:

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Status {
    Active,
    Inactive,
    Pending,
}
 
fn quick_reference() {
    // VARIANTS is a static constant
    const VARIANTS: &[&str] = Status::VARIANTS;
    
    // Access variant names
    assert_eq!(Status::VARIANTS, ["Active", "Inactive", "Pending"]);
    
    // Properties:
    // - Type: &'static [&'static str]
    // - Zero allocation
    // - Compile-time accessible
    // - Contains exact source code names
    // - Works with const contexts
    
    // Common uses:
    // - CLI help text
    // - Error messages
    // - Documentation generation
    // - Static lookup tables
    // - Input validation
    
    // Combines well with:
    // - EnumIter: for actual values
    // - EnumString: for parsing
    // - Display: for custom formatting
}
 
// Key insight:
// VariantNames provides compile-time access to variant names
// as static string slices, enabling zero-allocation name
// iteration and static validation at build time.

Key insight: strum::VariantNames solves the problem of "how do I get the names of all enum variants as strings?" by generating a const VARIANTS: &'static [&'static str] containing exactly those names. Unlike Display which converts runtime values to strings, or EnumIter which yields runtime variant values, VariantNames provides compile-time access to the string names themselves. The generated constant lives in static memory—the strings are embedded in the binary, and the slice is a static reference requiring no allocation or runtime initialization. Use VariantNames when you need to enumerate variant names for documentation, validation, error messages, or building lookup tables. Combine it with EnumString for bidirectional string-enum conversion, or with EnumIter when you need both names and values. The compile-time availability makes it ideal for const contexts and static assertions.