Loading page…
Rust walkthroughs
Loading page…
strum::EnumIter and how can it be used to iterate over all variants of an enum?strum::EnumIter is a derive macro from the strum crate that generates an iterator over all variants of an enum. This enables runtime enumeration of enum values, which Rust doesn't support natively, allowing patterns like validation, lookup tables, and exhaustive processing of enum variants.
Rust enums don't support iteration over their variants by default:
enum Status {
Active,
Inactive,
Pending,
}
// This won't work:
// for status in Status::iter() { ... }
// You'd have to manually create a list:
const ALL_STATUSES: [Status; 3] = [
Status::Active,
Status::Inactive,
Status::Pending,
];Maintaining a manual list is error-prone and violates the DRY principle.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Debug, Clone, Copy, EnumIter)]
enum Status {
Active,
Inactive,
Pending,
}
fn iterate_variants() {
// Now we can iterate
for status in Status::iter() {
println!("{:?}", status);
}
// Output:
// Active
// Inactive
// Pending
}The EnumIter derive adds an iter() method that yields all variants.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Debug, EnumIter)]
enum Color {
Red,
Green,
Blue,
}
fn iterator_usage() {
let mut iter = Color::iter();
assert_eq!(iter.next(), Some(Color::Red));
assert_eq!(iter.next(), Some(Color::Green));
assert_eq!(iter.next(), Some(Color::Blue));
assert_eq!(iter.next(), None);
// Can also collect
let all_colors: Vec<Color> = Color::iter().collect();
assert_eq!(all_colors.len(), 3);
}The iterator yields variants in the order they're defined in the enum.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(EnumIter)]
enum Direction {
North,
South,
East,
West,
}
// The derive implements IntoEnumIterator
fn trait_usage() {
// iter() comes from IntoEnumIterator
let variants: Vec<Direction> = Direction::iter().collect();
// Can also use the trait directly
fn count_variants<E: IntoEnumIterator>() -> usize {
E::iter().count()
}
assert_eq!(count_variants::<Direction>(), 4);
}The trait enables generic code that works with any iterable enum.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Debug, EnumIter)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn iterate_with_data() {
for msg in Message::iter() {
// Variants are constructed with default values
// for associated data
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(s) => println!("Write: {}", s),
Message::ChangeColor(r, g, b) => println!("Color: ({}, {}, {})", r, g, b),
}
}
// Output with default values:
// Quit
// Move to (0, 0)
// Write:
// Color: (0, 0, 0)
}Associated data uses Default::default() for the iteration.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
enum HttpStatus {
Ok,
NotFound,
InternalServerError,
}
impl HttpStatus {
fn code(&self) -> u16 {
match self {
HttpStatus::Ok => 200,
HttpStatus::NotFound => 404,
HttpStatus::InternalServerError => 500,
}
}
fn description(&self) -> &'static str {
match self {
HttpStatus::Ok => "Success",
HttpStatus::NotFound => "Not Found",
HttpStatus::InternalServerError => "Internal Server Error",
}
}
}
fn build_lookup() {
// Build code -> status map
let code_to_status: HashMap<u16, HttpStatus> = HttpStatus::iter()
.map(|status| (status.code(), status))
.collect();
assert_eq!(code_to_status.get(&200), Some(&HttpStatus::Ok));
assert_eq!(code_to_status.get(&404), Some(&HttpStatus::NotFound));
// Build description -> status map
let desc_to_status: HashMap<&'static str, HttpStatus> = HttpStatus::iter()
.map(|status| (status.description(), status))
.collect();
assert_eq!(desc_to_status.get("Not Found"), Some(&HttpStatus::NotFound));
}Iterating enables constructing maps from enum data.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Permission {
Read,
Write,
Delete,
Admin,
}
struct User {
name: String,
permissions: Vec<Permission>,
}
impl User {
fn has_all_permissions(&self) -> bool {
// Check if user has every permission
Permission::iter().all(|p| self.permissions.contains(&p))
}
fn missing_permissions(&self) -> Vec<Permission> {
Permission::iter()
.filter(|p| !self.permissions.contains(p))
.collect()
}
}
fn validation_example() {
let user = User {
name: "Alice".to_string(),
permissions: vec![Permission::Read, Permission::Write],
};
assert!(!user.has_all_permissions());
let missing = user.missing_permissions();
assert!(missing.contains(&Permission::Delete));
assert!(missing.contains(&Permission::Admin));
}Enum iteration enables exhaustive validation.
use strum::IntoEnumIterator;
use strum_macros::{EnumIter, EnumString, Display};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumString, Display)]
enum OutputFormat {
#[strum(serialize = "json")]
Json,
#[strum(serialize = "yaml")]
Yaml,
#[strum(serialize = "toml")]
Toml,
#[strum(serialize = "text")]
Text,
}
fn parse_format(input: &str) -> Result<OutputFormat, String> {
OutputFormat::from_str(input)
.map_err(|_| {
let valid_options: String = OutputFormat::iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("Invalid format. Valid options: {}", valid_options)
})
}
fn cli_example() {
match parse_format("json") {
Ok(format) => println!("Using: {:?}", format),
Err(e) => eprintln!("{}", e),
}
match parse_format("xml") {
Ok(format) => println!("Using: {:?}", format),
Err(e) => println!("Error: {}", e),
// Error: Invalid format. Valid options: json, yaml, toml, text
}
}Generate helpful error messages listing all valid options.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(EnumIter)]
enum CardSuit {
Hearts,
Diamonds,
Clubs,
Spades,
}
fn count_variants() {
let count = CardSuit::iter().count();
assert_eq!(count, 4);
// Useful for array sizing
let suit_counts: [usize; 4] = [0; CardSuit::iter().count()];
// Or use for capacity hints
let mut map = std::collections::HashMap::with_capacity(CardSuit::iter().count());
}Knowing the variant count at runtime helps with sizing data structures.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Operation {
Add,
Subtract,
Multiply,
Divide,
}
impl Operation {
fn apply(&self, a: f64, b: f64) -> Result<f64, &'static str> {
match self {
Operation::Add => Ok(a + b),
Operation::Subtract => Ok(a - b),
Operation::Multiply => Ok(a * b),
Operation::Divide => {
if b == 0.0 {
Err("Division by zero")
} else {
Ok(a / b)
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_all_operations_return_something() {
// Test that all operations work with valid input
for op in Operation::iter() {
let result = op.apply(10.0, 2.0);
assert!(result.is_ok(), "{:?} failed", op);
}
}
#[test]
fn test_division_by_zero() {
// Test specific case
let result = Operation::Divide.apply(10.0, 0.0);
assert!(result.is_err());
}
}Test exhaustively across all variants.
use strum::IntoEnumIterator;
use strum_macros::{EnumIter, EnumString, Display, AsRefStr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, EnumString, Display, AsRefStr)]
enum LogLevel {
#[strum(serialize = "error", to_string = "ERROR")]
Error,
#[strum(serialize = "warn", to_string = "WARN")]
Warn,
#[strum(serialize = "info", to_string = "INFO")]
Info,
#[strum(serialize = "debug", to_string = "DEBUG")]
Debug,
#[strum(serialize = "trace", to_string = "TRACE")]
Trace,
}
fn combined_features() {
// Iterate
for level in LogLevel::iter() {
// Display
println!("Level: {}", level);
// AsRefStr
println!("As ref: {}", level.as_ref());
}
// Parse from string
let parsed: LogLevel = "info".parse().unwrap();
assert_eq!(parsed, LogLevel::Info);
// All parsing options
let all_options: Vec<&str> = LogLevel::iter()
.map(|l| l.as_ref())
.collect();
assert_eq!(all_options, vec!["error", "warn", "info", "debug", "trace"]);
}Strum macros work together for comprehensive enum handling.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(EnumIter, Debug)]
enum Button { A, B, X, Y }
#[derive(EnumIter, Debug)]
enum Key { Up, Down, Left, Right }
fn print_all_variants<E: IntoEnumIterator + std::fmt::Debug>() {
for variant in E::iter() {
println!("{:?}", variant);
}
}
fn generic_example() {
print_all_variants::<Button>();
// A
// B
// X
// Y
print_all_variants::<Key>();
// Up
// Down
// Left
// Right
}Generic code can work with any iterable enum.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Clone, Copy, EnumIter)]
enum Small {
A, B, C,
}
#[derive(Clone, Copy, EnumIter)]
enum Large {
V00, V01, V02, V03, V04, V05, V06, V07, V08, V09,
V10, V11, V12, V13, V14, V15, V16, V17, V18, V19,
// ... hundreds more
}
fn performance() {
// Iteration is O(n) where n = number of variants
// Each iteration is O(1)
// For small enums, negligible overhead
let count = Small::iter().count(); // Very fast
// For large enums, still efficient
// Iterator yields values on demand, no allocation
let first_10: Vec<_> = Large::iter().take(10).collect();
// Can use iterator methods efficiently
let found = Large::iter().find(|v| matches!(v, Large::V05));
}The iterator is lazy and has minimal overhead.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Feature {
Auth,
Logging,
Metrics,
Cache,
Database,
Storage,
}
impl Feature {
fn is_infrastructure(&self) -> bool {
matches!(self, Feature::Database | Feature::Storage | Feature::Cache)
}
fn category(&self) -> &'static str {
match self {
Feature::Auth | Feature::Logging => "security",
Feature::Metrics => "observability",
Feature::Cache | Feature::Database | Feature::Storage => "infrastructure",
}
}
}
fn filtering_example() {
// Find by predicate
let infra_features: Vec<Feature> = Feature::iter()
.filter(|f| f.is_infrastructure())
.collect();
assert_eq!(infra_features.len(), 3);
// Group by category
use std::collections::HashMap;
let by_category: HashMap<&'static str, Vec<Feature>> = Feature::iter()
.fold(HashMap::new(), |mut acc, f| {
acc.entry(f.category()).or_default().push(f);
acc
});
assert_eq!(by_category.get("infrastructure").unwrap().len(), 3);
}Standard iterator methods work with enum iterators.
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
// Works only with enums, not structs or unions
// #[derive(EnumIter)]
// struct NotAnEnum; // Compile error
#[derive(EnumIter)]
enum EmptyEnum {}
fn empty_enum() {
// Iterating over empty enum yields nothing
assert_eq!(EmptyEnum::iter().count(), 0);
}
// Enums with many variants work fine
#[derive(EnumIter)]
enum ManyVariants {
V000, V001, V002, V003, V004, V005, V006, V007, V008, V009,
V010, V011, V012, V013, V014, V015, V016, V017, V018, V019,
// ... can have hundreds
}Associated data types must implement Default for iteration.
strum::EnumIter solves the problem of runtime enum iteration by generating an iterator that yields all variants:
Key benefits:
map, filter, findCommon use cases:
Limitations:
DefaultDefault associated data need manual implementationsThe EnumIter derive is part of the strum ecosystem, which includes other useful macros like EnumString (parsing), Display (formatting), and AsRefStr (string references), making it a comprehensive toolkit for enum handling in Rust.