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.
