What is 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.

The Problem: No Native Enum Iteration

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.

Adding EnumIter with Strum

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.

The Generated Iterator

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.

The IntoEnumIterator Trait

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.

Enums with Associated Data

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.

Building Lookup Tables

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.

Validation Patterns

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.

Command Line Argument Parsing

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.

Enum Variant Count

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.

Testing All Variants

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.

Combining with Other Strum Features

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.

Generic Functions with EnumIter

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.

Performance Considerations

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.

Filtering and Searching

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.

Limitations and Edge Cases

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.

Synthesis

strum::EnumIter solves the problem of runtime enum iteration by generating an iterator that yields all variants:

Key benefits:

  1. No manual maintenance: The iterator is automatically derived from the enum definition
  2. Type safety: Variants are actual enum values, not strings or integers
  3. Exhaustive processing: Guarantee you've handled all variants in loops
  4. Composable: Works with standard iterator methods like map, filter, find

Common use cases:

  • Building lookup tables from enum data
  • Validating that all variants meet some condition
  • Generating help text or documentation
  • CLI argument parsing with all valid options
  • Testing across all variants

Limitations:

  • Associated data types must implement Default
  • Only works with enums, not other types
  • Variants with non-Default associated data need manual implementations

The 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.