Rust enums are incredibly powerful for modeling data, but newcomers quickly discover a frustrating limitation: you cannot iterate through enum variants natively. This isn't an oversight but a deliberate design choice that keeps enums lightweight and efficient. However, when you need to loop through all possible enum values for configuration systems, command-line tools, or state machines, this limitation becomes a real problem.
The strum crate solves this elegantly by providing derive macros that extend enums with iteration capabilities and much more. We will show you exactly how to use strum to iterate through enum variants, convert enums to strings, parse strings back to enums, and leverage other powerful features that make working with enums much more pleasant.
The Problem: Why Rust Enums Don't Support Iteration
Consider this simple enum representing colors:
#[derive(Debug)]
enum Color {
Red,
Green,
Blue,
}
You might naturally try to iterate over all variants like this:
// This won't work!
for color in Color::iter() {
println!("{:?}", color);
}
This fails because Rust doesn't automatically generate iteration methods for enums. The compiler doesn't track enum variants as iterable collections at runtime, which keeps enums minimal and prevents unnecessary overhead. While this design philosophy maintains performance, it creates inconvenience when you genuinely need to work with all enum variants.
Other languages might provide built-in enum iteration, but Rust requires explicit implementation. This is where external crates become essential for extending functionality without compromising the language's core principles.
The Solution: Using strum for Enum Iteration
The strum crate provides the EnumIter
derive macro that generates the necessary code to make your enums iterable. Here's how to set it up and use it effectively.
Setting Up strum Dependencies
First, add strum to your Cargo.toml file:
[dependencies]
strum = "0.25"
strum_macros = "0.25"
Making Enums Iterable
Now you can derive EnumIter
on your enum and import the required trait:
use strum_macros::EnumIter;
use strum::IntoEnumIterator;
#[derive(Debug, EnumIter)]
enum Color {
Red,
Green,
Blue,
}
fn main() {
println!("All available colors:");
for color in Color::iter() {
println!("- {:?}", color);
}
}
This produces the output:
All available colors:
- Red
- Green
- Blue
The EnumIter
derive macro generates an iter()
method that returns an iterator over all enum variants. This iterator is type-safe and includes every variant you define, automatically updating when you add or remove enum variants.
Working with Complex Enums
strum works seamlessly with enums that have associated data, though you should note the structure
use strum_macros::EnumIter;
use strum::IntoEnumIterator;
#[derive(Debug, EnumIter)]
enum Command {
Start,
Stop,
Restart,
Status,
// Variants with data are skipped during iteration
Custom(String),
}
fn main() {
println!("Available commands:");
for command in Command::iter() {
println!("- {:?}", command);
}
}
Output:
Available commands:
- Start
- Stop
- Restart
- Status
- Custom("")
Beyond Iteration: Additional strum Features
strum
provides several other derive macros that enhance enum functionality significantly. These features work together to create more expressive and maintainable code.
String Conversion with Display
The Display
derive macro converts enums
to readable strings with customizable
output:
use strum_macros::Display;
#[derive(Display)]
enum Direction {
#[strum(serialize = "North")]
North,
#[strum(serialize = "South")]
South,
#[strum(serialize = "East")]
East,
#[strum(serialize = "West")]
West,
}
fn main() {
let direction = Direction::North;
println!("Heading: {}", direction); // Prints: Heading: North
// Also works with to_string()
let direction_string = Direction::South.to_string();
println!("Direction as string: {}", direction_string); // Prints: Direction as string: South
}
Without the serialize
attribute, strum uses the variant name directly. The serialize
attribute lets you customize the string representation for each variant.
Parsing Strings with EnumString
The EnumString
derive macro enables parsing strings into enum variants:
use strum_macros::EnumString;
use std::str::FromStr;
#[derive(Debug, EnumString)]
enum Status {
#[strum(serialize = "ok")]
Ok,
#[strum(serialize = "error")]
Error,
#[strum(serialize = "pending")]
Pending,
}
fn main() {
// Parse strings into enum variants
let status_ok: Status = "ok".parse().unwrap();
let status_error: Status = Status::from_str("error").unwrap();
println!("Parsed status 1: {:?}", status_ok); // Prints: Ok
println!("Parsed status 2: {:?}", status_error); // Prints: Error
// Handle parsing errors
match "invalid".parse::<Status>() {
Ok(status) => println!("Parsed: {:?}", status),
Err(e) => println!("Failed to parse: {}", e),
}
}
This is particularly useful for configuration files, command-line arguments, or API endpoints where you receive string values that need to be converted to strongly-typed enums.
Getting Static String References
The AsRefStr
derive macro provides efficient access to string representations:
use strum_macros::AsRefStr;
#[derive(AsRefStr)]
enum Role {
#[strum(serialize = "administrator")]
Admin,
#[strum(serialize = "regular_user")]
User,
#[strum(serialize = "guest")]
Guest,
}
fn check_permissions(role: Role) {
let role_str = role.as_ref();
println!("Checking permissions for: {}", role_str);
// Useful for database queries, logging, etc.
match role {
Role::Admin => println!("Full access granted"),
Role::User => println!("Limited access granted"),
Role::Guest => println!("Read-only access granted"),
}
}
fn main() {
check_permissions(Role::Admin);
check_permissions(Role::User);
}
Combining Multiple Derive Macros
You can combine multiple strum derive macros for maximum functionality:
use strum_macros::{EnumIter, Display, EnumString, AsRefStr};
use strum::IntoEnumIterator;
#[derive(Debug, EnumIter, Display, EnumString, AsRefStr)]
enum Priority {
#[strum(serialize = "low")]
Low,
#[strum(serialize = "medium")]
Medium,
#[strum(serialize = "high")]
High,
#[strum(serialize = "critical")]
Critical,
}
fn main() {
// Iteration
println!("All priority levels:");
for priority in Priority::iter() {
println!("- {}", priority); // Uses Display trait
}
// String parsing
let user_input = "high";
match user_input.parse::<Priority>() {
Ok(priority) => {
println!("Parsed priority: {}", priority);
println!("As reference: {}", priority.as_ref());
}
Err(e) => println!("Invalid priority: {}", e),
}
}
Practical Use Cases and Benefits
strum becomes invaluable in several common scenarios that every Rust developer encounters.
Configuration Systems: When building applications that read configuration files, you often need to validate string values against known options and iterate through all possible settings.
Command-Line Tools: CLI applications frequently need to display help text showing all available commands or options, making enum iteration essential.
State Machines: Game development and UI programming often involve state machines where you need to transition between states or display current state information.
API Development: REST APIs commonly need to serialize enums to JSON strings and deserialize user input back to enum variants.
The main benefits include saving development time by eliminating manual variant listing, providing compile-time safety when adding or removing enum variants, and improving code maintainability through automatic updates.
Common Pitfalls and Best Practices
When using strum, keep these considerations in mind. The serialize attribute only affects string conversion, not the actual enum variant names in your code. Be consistent with your serialization naming conventions across your application.
Summary
Rust's decision to not include built-in enum iteration maintains the language's performance principles while encouraging explicit design choices. The strum crate bridges this gap elegantly, providing not just iteration but a complete toolkit for working with enums effectively. For further learning checkout the rust reference on macros and the documentation on the strum-macro crate to see a full list of the macro capabilities.
If you have any questions reach out to me on my LinkedIn, and to learn more about rust , checkout our courses