Loading pageā¦
Rust walkthroughs
Loading pageā¦
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.