What is the purpose of strum::VariantNames for compile-time enum variant listing?

strum::VariantNames is a derive macro that generates a static array of string slices containing all enum variant names, accessible through the VARIANTS constant. Unlike runtime iteration through IntoEnumIterator, which instantiates each variant, VariantNames provides variant names as strings at compile time with zero runtime overhead—no allocations, no iterations, just a constant &'static [&'static str] array. This is ideal for generating documentation, validation error messages, CLI help text, or any scenario where you need the list of possible variants without constructing them.

Basic VariantNames Usage

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Status {
    Pending,
    Active,
    Completed,
    Failed,
}
 
fn main() {
    // VARIANTS is a static array of variant names
    println!("Status variants: {:?}", Status::VARIANTS);
    // Output: Status variants: ["Pending", "Active", "Completed", "Failed"]
    
    // It's a slice of string slices
    let variants: &[&str] = Status::VARIANTS;
    println!("Count: {}", variants.len());
    println!("First: {}", variants[0]);
}

The derive macro generates a VARIANTS constant containing all variant names as string slices.

Compile-Time Constant Generation

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Direction {
    North,
    South,
    East,
    West,
}
 
fn main() {
    // VARIANTS is generated at compile time
    // No runtime cost to create the list
    
    // The generated code is equivalent to:
    // impl strum::VariantNames for Direction {
    //     const VARIANTS: &'static [&'static str] = &["North", "South", "East", "West"];
    // }
    
    // Use in const contexts
    const VARIANT_COUNT: usize = Direction::VARIANTS.len();
    println!("Variant count: {}", VARIANT_COUNT);
    
    // Use in static contexts
    static DIRECTION_NAMES: &[&str] = Direction::VARIANTS;
    println!("Names: {:?}", DIRECTION_NAMES);
}

VARIANTS is a compile-time constant with zero runtime overhead.

Comparison with IntoEnumIterator

use strum::{VariantNames, IntoEnumIterator};
 
#[derive(VariantNames, IntoEnumIterator, Debug)]
enum Color {
    Red,
    Green,
    Blue,
}
 
fn main() {
    // VariantNames: static array of strings
    let names: &[&str] = Color::VARIANTS;
    println!("Names: {:?}", names);
    
    // IntoEnumIterator: iterate over actual variants
    let variants: Vec<Color> = Color::iter().collect();
    println!("Variants: {:?}", variants);
    
    // Key differences:
    // 1. VariantNames gives strings; IntoEnumIterator gives variants
    // 2. VariantNames is O(1) access; IntoEnumIterator is O(n) iteration
    // 3. VariantNames works without variant values; IntoEnumIterator needs them constructible
    // 4. VariantNames has no allocation; IntoEnumIterator may allocate if collected
    
    // VariantNames is useful when you only need names
    println!("Available colors: {}", Color::VARIANTS.join(", "));
    
    // IntoEnumIterator is useful when you need to operate on variants
    for color in Color::iter() {
        println!("Color: {:?}", color);
    }
}

VariantNames provides just names; IntoEnumIterator provides actual variant values.

Enum Variant with Associated Data

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
 
fn main() {
    // VariantNames lists variant names, ignoring associated data
    println!("Message variants: {:?}", Message::VARIANTS);
    // Output: ["Quit", "Move", "Write", "ChangeColor"]
    
    // The associated data types are not included in the names
    // This is useful for identifying which variants exist
    // without needing to construct them
    
    // For validation or documentation:
    println!("Valid message types: {}", Message::VARIANTS.join(", "));
}

Variant names are extracted without considering associated data types.

Display Names with strum

use strum::VariantNames;
 
#[derive(VariantNames)]
#[strum(serialize_all = "snake_case")]
enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
    Patch,
}
 
fn main() {
    // With serialize_all = "snake_case"
    println!("Methods: {:?}", HttpMethod::VARIANTS);
    // Output: ["get", "post", "put", "delete", "patch"]
    
    #[derive(VariantNames)]
    #[strum(serialize_all = "kebab_case")]
    enum ContentType {
        ApplicationJson,
        TextHtml,
        MultipartFormData,
    }
    
    println!("Content types: {:?}", ContentType::VARIANTS);
    // Output: ["application-json", "text-html", "multipart-form-data"]
    
    #[derive(VariantNames)]
    #[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
    enum ConfigKey {
        MaxConnections,
        TimeoutSeconds,
        RetryCount,
    }
    
    println!("Keys: {:?}", ConfigKey::VARIANTS);
    // Output: ["MAX_CONNECTIONS", "TIMEOUT_SECONDS", "RETRY_COUNT"]
}

serialize_all attribute controls how variant names are formatted.

Custom Variant Serialization

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Priority {
    #[strum(serialize = "critical")]
    High,
    #[strum(serialize = "normal")]
    Medium,
    #[strum(serialize = "low_priority")]
    Low,
}
 
fn main() {
    // Custom serialization in VARIANTS
    println!("Priorities: {:?}", Priority::VARIANTS);
    // Output: ["critical", "normal", "low_priority"]
    
    // Multiple serialization forms (first one is used for VARIANTS)
    #[derive(VariantNames)]
    enum Status {
        #[strum(serialize = "pending", serialize = "waiting")]
        Pending,
        #[strum(serialize = "active", serialize = "running")]
        Active,
    }
    
    println!("Status: {:?}", Status::VARIANTS);
    // Uses first serialize value: ["pending", "active"]
}

Custom serialize attributes override default variant names.

Validation Use Case

use strum::VariantNames;
 
#[derive(VariantNames, Debug)]
enum Role {
    Admin,
    User,
    Guest,
}
 
fn validate_role(input: &str) -> Result<Role, String> {
    // Use VARIANTS for validation without constructing values
    if Role::VARIANTS.contains(&input) {
        // In real code, you'd use FromStr or similar to convert
        // This example shows the validation pattern
        Ok(match input {
            "Admin" => Role::Admin,
            "User" => Role::User,
            "Guest" => Role::Guest,
            _ => unreachable!(),
        })
    } else {
        Err(format!(
            "Invalid role '{}'. Valid roles are: {}",
            input,
            Role::VARIANTS.join(", ")
        ))
    }
}
 
fn main() {
    println!("Valid roles: {:?}", Role::VARIANTS);
    
    match validate_role("Admin") {
        Ok(role) => println!("Valid: {:?}", role),
        Err(e) => println!("Error: {}", e),
    }
    
    match validate_role("SuperAdmin") {
        Ok(role) => println!("Valid: {:?}", role),
        Err(e) => println!("Error: {}", e),
    }
}

Use VARIANTS for input validation and error messages.

CLI Help Text Generation

use strum::VariantNames;
 
#[derive(VariantNames)]
enum OutputFormat {
    Json,
    Yaml,
    Toml,
    Text,
}
 
fn print_help() {
    println!("Usage: myapp --format <FORMAT>");
    println!();
    println!("Formats:");
    for format in OutputFormat::VARIANTS {
        println!("  {}", format.to_lowercase());
    }
}
 
fn main() {
    print_help();
    // Output:
    // Usage: myapp --format <FORMAT>
    //
    // Formats:
    //   json
    //   yaml
    //   toml
    //   text
    
    // Available at compile time for generated documentation
    const FORMATS: &[&str] = OutputFormat::VARIANTS;
    println!("Number of formats: {}", FORMATS.len());
}

Generate CLI help text from enum variant names.

API Documentation Generation

use strum::VariantNames;
 
#[derive(VariantNames)]
#[strum(serialize_all = "snake_case")]
enum Endpoint {
    GetUser,
    CreateUser,
    UpdateUser,
    DeleteUser,
    ListUsers,
}
 
fn generate_api_docs() -> String {
    let mut docs = String::from("Available Endpoints:\n");
    
    for endpoint in Endpoint::VARIANTS {
        docs.push_str(&format!("- GET /api/{}\n", endpoint));
    }
    
    docs
}
 
fn main() {
    println!("{}", generate_api_docs());
    // Output:
    // Available Endpoints:
    // - GET /api/get_user
    // - GET /api/create_user
    // - GET /api/update_user
    // - GET /api/delete_user
    // - GET /api/list_users
    
    // Use for OpenAPI/Swagger documentation
    println!("OpenAPI paths:");
    for path in Endpoint::VARIANTS {
        println!("  /api/{}:", path);
        println!("    get:");
        println!("      summary: {} endpoint", path);
    }
}

Generate API documentation from variant names.

UI Dropdown Population

use strum::VariantNames;
 
#[derive(VariantNames)]
#[strum(serialize_all = "title_case")]
enum Country {
    UnitedStates,
    UnitedKingdom,
    Canada,
    Australia,
    Germany,
}
 
fn render_dropdown() -> String {
    let mut html = String::from("<select name=\"country\">\n");
    
    for country in Country::VARIANTS {
        html.push_str(&format!("  <option value=\"{}\">{}</option>\n", 
            country.to_lowercase().replace(" ", "_"), country));
    }
    
    html.push_str("</select>");
    html
}
 
fn main() {
    println!("{}", render_dropdown());
    // <select name="country">
    //   <option value="united_states">United States</option>
    //   <option value="united_kingdom">United Kingdom</option>
    //   <option value="canada">Canada</option>
    //   <option value="australia">Australia</option>
    //   <option value="germany">Germany</option>
    // </select>
    
    // Zero allocation at runtime for the names themselves
    // They're compiled into the binary
}

Populate UI dropdowns from compile-time constant.

Zero-Cost Variant Listing

use strum::VariantNames;
 
#[derive(VariantNames)]
enum Large {
    V1, V2, V3, V4, V5,
    V6, V7, V8, V9, V10,
}
 
fn main() {
    // VARIANTS is a static reference
    // No heap allocation
    // No iteration needed to build the list
    
    let names: &'static [&'static str] = Large::VARIANTS;
    
    // Compare to runtime approach:
    // - Would need to iterate
    // - Would allocate Vec
    // - Would construct strings
    
    // VARIANTS is available instantly
    println!("Names: {:?}", names);
    
    // Can be used in const expressions
    const COUNT: usize = Large::VARIANTS.len();
    println!("Count: {}", COUNT);
    
    // String slices are 'static
    let first: &'static str = Large::VARIANTS[0];
    println!("First: {}", first);
}

VARIANTS is a static reference with no allocation or construction cost.

Combining with Other Strum Traits

use strum::{VariantNames, IntoEnumIterator, Display, AsRefStr};
 
#[derive(VariantNames, IntoEnumIterator, Display, AsRefStr, Debug)]
#[strum(serialize_all = "snake_case")]
enum State {
    Initial,
    Processing,
    Complete,
    Error,
}
 
fn main() {
    // VariantNames: compile-time list of names
    println!("Names: {:?}", State::VARIANTS);
    
    // IntoEnumIterator: iterate over values
    println!("Values: {:?}", State::iter().collect::<Vec<_>>());
    
    // Display: format variant as string
    println!("Display: {}", State::Initial);
    
    // AsRefStr: get variant name as &str
    println!("AsRefStr: {}", State::Processing.as_ref());
    
    // Use together for complete enum introspection
    println!("All states:");
    for state in State::iter() {
        println!("  {} (as ref: {})", state, state.as_ref());
    }
    println!("Names from VARIANTS: {:?}", State::VARIANTS);
}

Combine VariantNames with other traits for comprehensive enum introspection.

Generic Functions with VariantNames

use strum::VariantNames;
 
fn print_variants<T: strum::VariantNames>() {
    println!("Variants: {:?}", T::VARIANTS);
}
 
fn contains_variant<T: strum::VariantNames>(name: &str) -> bool {
    T::VARIANTS.contains(&name)
}
 
#[derive(VariantNames)]
enum Priority {
    Low,
    Medium,
    High,
}
 
#[derive(VariantNames)]
enum Status {
    Pending,
    Active,
    Done,
}
 
fn main() {
    print_variants::<Priority>();
    print_variants::<Status>();
    
    println!("Priority contains 'High': {}", contains_variant::<Priority>("High"));
    println!("Status contains 'Unknown': {}", contains_variant::<Status>("Unknown"));
}

Use generics to write functions that work with any VariantNames type.

Static Initialization Patterns

use strum::VariantNames;
use std::collections::HashSet;
 
#[derive(VariantNames)]
enum Permission {
    Read,
    Write,
    Delete,
    Admin,
}
 
// Static HashSet for O(1) lookup
lazy_static::lazy_static! {
    static ref PERMISSION_SET: HashSet<&'static str> = {
        Permission::VARIANTS.iter().copied().collect()
    };
}
 
fn is_valid_permission(perm: &str) -> bool {
    PERMISSION_SET.contains(perm)
}
 
fn main() {
    println!("Valid permissions: {:?}", Permission::VARIANTS);
    println!("Is 'Read' valid: {}", is_valid_permission("Read"));
    println!("Is 'Execute' valid: {}", is_valid_permission("Execute"));
    
    // Build once at startup, use many times
    // VARIANTS provides the static strings
}

Build static collections from VARIANTS for efficient lookups.

Synthesis

Comparison with other approaches:

Approach Output Runtime Cost Use Case
VariantNames &[&str] Zero Names only, static
IntoEnumIterator Iterator of variants O(n) iteration Operate on values
Display Formatted string Per call Single variant name
AsRefStr &str reference Minimal Single variant name

Trait vs constant:

Aspect Behavior
Definition const VARIANTS: &'static [&'static str]
Access EnumType::VARIANTS
Lifetime 'static
Allocation None (static data)
Type &'static [&'static str]

Serialization formats:

Attribute Input Output
Default VariantName "VariantName"
snake_case VariantName "variant_name"
kebab_case VariantName "variant-name"
SCREAMING_SNAKE_CASE VariantName "VARIANT_NAME"
serialize = "..." Any Custom string

Key insight: strum::VariantNames generates a compile-time constant VARIANTS array containing all enum variant names as string slices, providing zero-overhead access to the list of possible variants without instantiating any of them. This is fundamentally different from IntoEnumIterator, which iterates over actual variant values at runtime—the former gives you strings for free, the latter gives you values at the cost of iteration. Use VariantNames when you need variant names for documentation, validation, error messages, or UI generation—any situation where you need "what variants exist" rather than "operate on each variant." The static lifetime of the strings (&'static str) means they can be used in const expressions, stored in static collections, and embedded directly in the binary with no runtime allocation. Combine with serialize_all and custom serialize attributes to control how names are formatted, making it suitable for generating CLI help, API docs, or user-facing strings directly from the type definition.