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.
