What is the purpose of strum::VariantNames for introspecting enum variant names at runtime?

strum::VariantNames is a derive macro that generates a VARIANTS constant containing an array of all enum variant names as string slices, enabling runtime introspection of enum variants without requiring values. Unlike iteration-based approaches, VariantNames provides variant names statically at compile time, useful for validation, documentation generation, and user interfaces where you need to know what variants exist without constructing them.

Basic VariantNames Derive

use strum::VariantNames;
 
#[derive(Debug, VariantNames)]
enum Color {
    Red,
    Green,
    Blue,
}
 
fn basic_usage() {
    // VariantNames generates a VARIANTS constant
    // It's a static array of variant name strings
    
    println!("Available colors: {:?}", Color::VARIANTS);
    // Output: ["Red", "Green", "Blue"]
    
    // The type is &'static [&'static str]
    let variants: &'static [&'static str] = Color::VARIANTS;
    assert_eq!(variants.len(), 3);
}

Deriving VariantNames provides VARIANTS as a static array of variant name strings.

Static Availability Without Values

use strum::VariantNames;
 
#[derive(Debug, VariantNames)]
enum Status {
    Pending,
    InProgress,
    Completed,
    Failed,
}
 
fn without_values() {
    // VARIANTS is available without constructing any enum values
    // This is the key difference from IntoEnumIterator
    
    // You don't need a Status instance to get the names
    // VARIANTS exists as a static constant on the type
    
    // This works even if variants have fields that can't be easily constructed:
    
    #[derive(Debug, VariantNames)]
    enum ComplexStatus {
        Pending,
        InProgress { started_at: u64, assigned_to: String },
        Completed { finished_at: u64 },
        Failed(String),
    }
    
    // VARIANTS still works:
    assert_eq!(ComplexStatus::VARIANTS, ["Pending", "InProgress", "Completed", "Failed"]);
    
    // You can't iterate ComplexStatus values easily, but you can get names
}

VARIANTS provides names statically without requiring value construction.

The Generated Constant

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Direction {
    North,
    South,
    East,
    West,
}
 
fn generated_code() {
    // VariantNames generates something equivalent to:
    //
    // impl Direction {
    //     pub const VARIANTS: &'static [&'static str] = 
    //         &["North", "South", "East", "West"];
    // }
    
    // It's a const, not a computed value
    // Available at compile time for const contexts
    
    const NUM_DIRECTIONS: usize = Direction::VARIANTS.len();
    assert_eq!(NUM_DIRECTIONS, 4);
    
    // Zero runtime overhead - it's a static slice
}

VARIANTS is a const static slice with zero runtime allocation.

Use Case: Input Validation

use strum::VariantNames;
 
#[derive(Debug, VariantNames)]
enum Role {
    Admin,
    Moderator,
    User,
    Guest,
}
 
fn validate_role(input: &str) -> Result<Role, String> {
    // Check if input matches any variant name
    
    if Role::VARIANTS.contains(&input) {
        // In a real implementation, you'd parse to the enum
        // This example shows validation
        
        match input {
            "Admin" => Ok(Role::Admin),
            "Moderator" => Ok(Role::Moderator),
            "User" => Ok(Role::User),
            "Guest" => Ok(Role::Guest),
            _ => unreachable!(), // We checked with VARIANTS
        }
    } else {
        Err(format!(
            "Invalid role: '{}'. Valid roles: {:?}",
            input,
            Role::VARIANTS
        ))
    }
}
 
fn validation_example() {
    assert!(validate_role("Admin").is_ok());
    assert!(validate_role("InvalidRole").is_err());
}

VARIANTS enables validation against known variant names without iterating all values.

Use Case: CLI Argument Completion

use strum::VariantNames;
use std::env;
 
#[derive(Debug, VariantNames)]
enum OutputFormat {
    Json,
    Yaml,
    Toml,
    Csv,
}
 
fn cli_usage() {
    // Generate help text dynamically
    println!("Available output formats:");
    for variant in OutputFormat::VARIANTS {
        println!("  - {}", variant);
    }
    
    // Generate shell completion suggestions
    // The static VARIANTS can be used for autocomplete
    
    let args: Vec<String> = env::args().collect();
    if args.len() > 1 && args[1] == "--complete" {
        // Shell completion: print all variants
        for variant in OutputFormat::VARIANTS {
            println!("{}", variant);
        }
    }
}

VARIANTS provides variant names for help text and shell completion.

Comparison with IntoEnumIterator

use strum::{VariantNames, IntoEnumIterator, EnumIter};
 
#[derive(Debug, VariantNames, EnumIter)]
enum Size {
    Small,
    Medium,
    Large,
}
 
fn comparison() {
    // VariantNames: static array of names, no values needed
    let names: &'static [&'static str] = Size::VARIANTS;
    
    // IntoEnumIterator: iterate actual enum values
    let values: Vec<Size> = Size::iter().collect();
    
    // VARIANTS advantages:
    // 1. Available without constructing values
    // 2. Works with variants that have fields
    // 3. Zero runtime cost (static slice)
    // 4. Usable in const contexts
    
    // IntoEnumIterator advantages:
    // 1. Gives actual values, not just names
    // 2. Can call methods on values
    // 3. Can transform values
    
    // Use VARIANTS when:
    // - You only need names, not values
    // - Variants have complex fields
    // - Zero overhead is critical
    
    // Use IntoEnumIterator when:
    // - You need the actual enum values
    // - You want to call methods on values
    // - Variant fields are easily constructed
}

VARIANTS gives names without values; IntoEnumIterator gives actual values.

Handling Variants with Fields

use strum::VariantNames;
 
#[derive(Debug, VariantNames)]
enum Message {
    Text(String),
    Binary { data: Vec<u8>, encoding: String },
    Empty,
}
 
fn variants_with_fields() {
    // VARIANTS works even when variants have fields
    // The field names are NOT included, just variant names
    
    assert_eq!(Message::VARIANTS, ["Text", "Binary", "Empty"]);
    
    // This is useful because IntoEnumIterator would require
    // default values or IntoEnumIterator derive with strum(default)
    // for each field, which may not be possible
    
    // VARIANTS bypasses the need for field values entirely
}

VARIANTS provides variant names regardless of field types or complexity.

Customizing with strum Attributes

use strum::VariantNames;
 
#[derive(Debug, VariantNames)]
enum Priority {
    #[strum(to_string = "critical")]
    High,
    #[strum(to_string = "normal")]
    Medium,
    #[strum(to_string = "low")]
    Low,
}
 
fn customizing_names() {
    // Note: VariantNames uses the original variant names by default
    // The to_string attribute affects AsRefStr and Display, not VARIANTS
    
    assert_eq!(Priority::VARIANTS, ["High", "Medium", "Low"]);
    
    // For customized strings, use AsRefStr instead:
    // Priority::iter().map(|p| p.as_ref()).collect()
}

VARIANTS contains variant names, not customized to_string values.

Use Case: Serialization Format Validation

use strum::VariantNames;
use serde::{Deserialize, Serialize};
 
#[derive(Debug, VariantNames, Serialize, Deserialize)]
enum EventType {
    Created,
    Updated,
    Deleted,
}
 
fn serialization_validation() {
    // When deserializing, validate against known variants
    
    fn validate_event_type(json_str: &str) -> Result<EventType, String> {
        // Parse as string first to validate
        let raw: String = serde_json::from_str(json_str)
            .map_err(|e| format!("Invalid JSON: {}", e))?;
        
        if !EventType::VARIANTS.contains(&raw.as_str()) {
            return Err(format!(
                "Unknown event type: '{}'. Valid types: {:?}",
                raw, EventType::VARIANTS
            ));
        }
        
        // Now safely deserialize
        serde_json::from_str(json_str)
            .map_err(|e| format!("Deserialization error: {}", e))
    }
    
    assert!(validate_event_type(r#""Created""#).is_ok());
    assert!(validate_event_type(r#""Invalid""#).is_err());
}

VARIANTS validates external data against known variant names.

Use Case: Documentation Generation

use strum::VariantNames;
 
#[derive(Debug, VariantNames)]
enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
    Patch,
    Head,
    Options,
    Trace,
    Connect,
}
 
fn generate_documentation() {
    // Generate documentation or API specs from VARIANTS
    
    let mut doc = String::new();
    doc.push_str("# HTTP Methods\n\n");
    doc.push_str("Supported methods:\n\n");
    
    for variant in HttpMethod::VARIANTS {
        doc.push_str(&format!("- `{}`\n", variant));
    }
    
    // doc now contains:
    // # HTTP Methods
    // 
    // Supported methods:
    // 
    // - `Get`
    // - `Post`
    // ...
}

VARIANTS enables programmatic documentation generation.

Const Usage

use strum::VariantNames;
 
#[derive(Debug, VariantNames)]
enum Setting {
    Theme,
    Language,
    Timezone,
    Notifications,
}
 
// VARIANTS can be used in const contexts
const NUM_SETTINGS: usize = Setting::VARIANTS.len();
 
fn const_usage() {
    // Available at compile time
    const SETTINGS_LIST: &'static [&'static str] = Setting::VARIANTS;
    
    // Can be used for array sizing
    let setting_defaults: [&str; NUM_SETTINGS] = ["default"; 4];
    
    // Can be used in static assertions
    static_assertions::const_assert!(Setting::VARIANTS.len() == 4);
}

VARIANTS is a const, usable in compile-time contexts.

Integration with Other Strum Traits

use strum::{VariantNames, IntoEnumIterator, AsRefStr, EnumIter};
 
#[derive(Debug, Clone, VariantNames, EnumIter, AsRefStr)]
enum Animal {
    Dog,
    Cat,
    Bird,
    Fish,
}
 
fn combined_usage() {
    // VariantNames: static names
    let names = Animal::VARIANTS;
    
    // IntoEnumIterator: iterate values
    let values: Vec<Animal> = Animal::iter().collect();
    
    // AsRefStr: convert values to strings
    let refs: Vec<&str> = Animal::iter().map(|a| a.as_ref()).collect();
    
    // All three give the same strings, but differently:
    
    // VARIANTS:
    // - Static slice
    // - No allocation
    // - Names only
    
    // iter().map(as_ref):
    // - Dynamic allocation
    // - Has values available
    // - Can use custom to_string
    
    // Use case determines which to use
}

VARIANTS complements other strum traits; choose based on needs.

Performance Characteristics

use strum::VariantNames;
 
#[derive(VariantNames)]
enum LargeEnum {
    Variant000, Variant001, Variant002, /* ... 100 variants ... */
}
 
fn performance() {
    // VARIANTS has zero runtime allocation
    
    // It's a &'static [&'static str]
    // Stored in the binary, not heap allocated
    
    // Iteration is just pointer arithmetic
    for name in LargeEnum::VARIANTS {
        // No allocation, no string construction
    }
    
    // Contains check is O(n) but on static data
    if LargeEnum::VARIANTS.contains(&"Variant050") {
        // ...
    }
    
    // For frequent lookups, consider building a HashSet
    // But for most cases, linear scan is fine
    // given small enum sizes and static data
}

VARIANTS is static data with zero runtime allocation.

Use Case: Configuration Keys

use strum::VariantNames;
use std::collections::HashMap;
 
#[derive(Debug, VariantNames)]
enum ConfigKey {
    DatabaseUrl,
    MaxConnections,
    Timeout,
    LogLevel,
}
 
fn config_keys() {
    // Validate configuration keys from file
    
    let config_keys: HashMap<&str, &str> = [
        ("DatabaseUrl", "postgres://..."),
        ("MaxConnections", "10"),
        ("Timeout", "30"),
    ].into_iter().collect();
    
    // Check for unknown keys
    for key in config_keys.keys() {
        if !ConfigKey::VARIANTS.contains(key) {
            eprintln!("Warning: Unknown config key: {}", key);
        }
    }
    
    // Generate default config
    let mut defaults = HashMap::new();
    for key in ConfigKey::VARIANTS {
        defaults.insert(*key, "");
    }
}

VARIANTS validates configuration keys against known options.

Complete Summary

use strum::VariantNames;
 
fn complete_summary() {
    // ┌─────────────────────────────────────────────────────────────────────────┐
    // │ Feature              │ Description                                     │
    // ├─────────────────────────────────────────────────────────────────────────┤
    // │ Generated constant   │ pub const VARIANTS: &'static [&'static str]    │
    // │ Content              │ Variant names as strings (not to_string)        │
    // │ Availability         │ Static, no values needed                        │
    // │ Runtime cost         │ Zero allocation, static slice                  │
    // │ Const usable         │ Yes, available at compile time                  │
    // │ Field handling       │ Works with any variant fields                   │
    // └─────────────────────────────────────────────────────────────────────────┘
    
    // When to use VariantNames:
    
    // 1. Input validation
    //    - Check if user input matches a variant name
    //    - VARIANTS.contains(&input)
    
    // 2. Documentation generation
    //    - List available options dynamically
    //    - Generate API specs
    
    // 3. CLI completion
    //    - Shell tab completion
    //    - Help text generation
    
    // 4. Configuration validation
    //    - Validate config keys
    //    - Check for unknown keys
    
    // 5. When variants have complex fields
    //    - IntoEnumIterator may not work
    //    - VARIANTS always works
    
    // When NOT to use VariantNames:
    
    // 1. Need actual values
    //    - Use IntoEnumIterator instead
    
    // 2. Need custom string representations
    //    - Use AsRefStr with to_string attributes
    
    // 3. Need associated data
    //    - Use IntoEnumIterator and access fields
}
 
// Key insight:
// strum::VariantNames generates a VARIANTS constant that provides
// enum variant names as a static slice. This enables:
//
// - Runtime introspection without constructing values
// - Zero-overhead access to variant names (static slice)
// - Working with variants that have complex fields
// - Compile-time usage in const contexts
//
// The key advantage over IntoEnumIterator is that VARIANTS
// doesn't require constructing enum values. This matters when:
// - Variants have fields that are hard to construct
// - You only need names, not values
// - You want zero runtime overhead
// - You need const-compatible access
//
// VariantNames works alongside other strum traits:
// - AsRefStr: customize string representation
// - IntoEnumIterator: iterate actual values
// - Display: format enum values
//
// Use VariantNames when you need just the names and want
// the simplicity and performance of a static slice.

Key insight: strum::VariantNames provides a static VARIANTS constant containing all variant names as string slices, enabling runtime introspection without value construction. Unlike IntoEnumIterator, which requires constructing values, VARIANTS works with any variant fields and has zero runtime allocation. Use it for validation, documentation generation, CLI completion, and any scenario where you need variant names but not values. The constant is available at compile time and stored as a static slice, making it ideal for performance-sensitive or const contexts.