How does strum::IntoEnumIterator::iter enable runtime iteration over enum variants?

IntoEnumIterator::iter generates an iterator that yields every variant of an enum at runtime, enabling patterns that would otherwise require manual listing or procedural macros. The strum crate provides the EnumIter derive macro which generates the implementation code, creating an iterator that knows about all variants without reflection. This transforms enums from static type definitions into runtime collections you can loop over, filter, search, or transform—useful for CLI argument parsing, configuration validation, documentation generation, and any scenario where you need to enumerate all possible values.

Deriving EnumIter

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Color {
    Red,
    Green,
    Blue,
}
 
fn main() {
    // iter() returns an iterator over all variants
    for color in Color::iter() {
        println!("{:?}", color);
    }
    // Output: Red, Green, Blue (in definition order)
}

The EnumIter derive macro generates an IntoEnumIterator implementation.

Basic Iteration Patterns

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Status {
    Pending,
    InProgress,
    Completed,
    Failed,
}
 
fn main() {
    // Collect all variants into a Vec
    let all_statuses: Vec<Status> = Status::iter().collect();
    assert_eq!(all_statuses.len(), 4);
    
    // Find a specific variant
    let completed = Status::iter().find(|&s| s == Status::Completed);
    assert!(completed.is_some());
    
    // Count variants
    let count = Status::iter().count();
    println!("{} status variants", count);
    
    // Check if any match a condition
    let has_failed = Status::iter().any(|s| matches!(s, Status::Failed));
    assert!(has_failed);
}

iter() returns a standard Iterator, enabling all iterator methods.

Runtime Validation and Lookup

use strum::IntoEnumIterator;
use strum::Display;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, Display)]
enum Role {
    Admin,
    Moderator,
    User,
    Guest,
}
 
fn from_str(s: &str) -> Option<Role> {
    // Iterate to find matching variant by display string
    Role::iter().find(|&r| r.to_string() == s)
}
 
fn main() {
    assert_eq!(from_str("Admin"), Some(Role::Admin));
    assert_eq!(from_str("Unknown"), None);
    
    // Case-insensitive lookup
    let role = Role::iter()
        .find(|r| r.to_string().to_lowercase() == "moderator");
    assert_eq!(role, Some(Role::Moderator));
    
    // Validate configuration value
    let config_role = "SuperUser";
    if !Role::iter().any(|r| r.to_string() == config_role) {
        println!("Invalid role: {}", config_role);
    }
}

Enable string-to-variant lookup without hardcoding all values.

CLI Argument Parsing

use strum::IntoEnumIterator;
use clap::ValueEnum;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, ValueEnum)]
enum Format {
    Json,
    Yaml,
    Toml,
    Xml,
}
 
fn main() {
    // Generate possible values for help text
    let possible_values: Vec<String> = Format::iter()
        .map(|f| format!("{:?}", f))
        .collect();
    
    println!("Available formats: {}", possible_values.join(", "));
    
    // Validate user input
    let user_input = "json";
    let format = Format::iter()
        .find(|f| format!("{:?}", f).to_lowercase() == user_input);
    
    match format {
        Some(f) => println!("Using format: {:?}", f),
        None => println!("Invalid format. Choose from: {}", 
            Format::iter()
                .map(|f| format!("{:?}", f))
                .collect::<Vec<_>>()
                .join(", ")
        ),
    }
}

iter() enables dynamic validation and help text generation for CLI tools.

Combined with Other Strum Traits

use strum::{IntoEnumIterator, Display, EnumString, AsRefStr};
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, Display, EnumString, AsRefStr)]
enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
    Patch,
}
 
fn main() {
    // Display trait for string representation
    for method in HttpMethod::iter() {
        println!("Method: {}", method);  // Get, Post, etc.
    }
    
    // Parse from string
    use std::str::FromStr;
    let parsed: HttpMethod = HttpMethod::from_str("Post").unwrap();
    assert_eq!(parsed, HttpMethod::Post);
    
    // Iterate for validation
    fn is_valid_method(s: &str) -> bool {
        HttpMethod::iter().any(|m| m.as_ref() == s)
    }
    
    assert!(is_valid_method("Get"));
    assert!(!is_valid_method("HEAD"));
}

Combine EnumIter with Display, EnumString, and AsRefStr for complete enum handling.

Configuration Generation

use strum::IntoEnumIterator;
use serde::Serialize;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, Serialize)]
enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}
 
fn generate_config_template() -> String {
    let mut config = String::new();
    config.push_str("[logging]\n");
    config.push_str("# Available levels: ");
    
    // List all possible values in documentation
    let levels: Vec<&str> = LogLevel::iter()
        .map(|l| format!("{:?}", l).to_lowercase())
        .collect();
    config.push_str(&levels.join(", "));
    config.push_str("\n");
    config.push_str("level = \"info\"\n");
    
    config
}
 
fn validate_config(level: &str) -> bool {
    LogLevel::iter()
        .any(|l| format!("{:?}", l).to_lowercase() == level.to_lowercase())
}
 
fn main() {
    println!("{}", generate_config_template());
    
    assert!(validate_config("info"));
    assert!(validate_config("DEBUG"));
    assert!(!validate_config("verbose"));
}

Generate documentation and validate configuration values dynamically.

Testing All Variants

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum State {
    Initial,
    Processing,
    Success,
    Failure,
}
 
impl State {
    fn is_terminal(&self) -> bool {
        matches!(self, State::Success | State::Failure)
    }
    
    fn can_transition_to(&self, next: State) -> bool {
        use State::*;
        match self {
            Initial => matches!(next, Processing),
            Processing => matches!(next, Success | Failure),
            Success | Failure => false,
        }
    }
}
 
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_all_states_have_valid_transitions() {
        // Test all states without manually listing them
        for state in State::iter() {
            // Every state should have defined terminal behavior
            if state.is_terminal() {
                assert!(!state.can_transition_to(State::Initial));
            }
        }
    }
    
    #[test]
    fn test_no_self_transitions_from_terminal() {
        for state in State::iter() {
            if state.is_terminal() {
                assert!(!state.can_transition_to(state));
            }
        }
    }
}
 
fn main() {
    // Run tests for all states
    for state in State::iter() {
        println!("{:?} - terminal: {}", state, state.is_terminal());
    }
}

Test code coverage for all variants without hardcoding each one.

Enum Counting and Statistics

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Permission {
    Read,
    Write,
    Delete,
    Admin,
}
 
fn main() {
    // Count total variants
    let total = Permission::iter().count();
    println!("Total permissions: {}", total);
    
    // Create a bitset for permissions
    let mut granted: u8 = 0;
    
    fn permission_bit(p: Permission) -> u8 {
        1 << (p as usize)
    }
    
    // Grant all permissions
    for perm in Permission::iter() {
        granted |= permission_bit(perm);
    }
    
    // Check if all are granted
    for perm in Permission::iter() {
        let has_perm = granted & permission_bit(perm) != 0;
        println!("{:?}: {}", perm, has_perm);
    }
}

Use iter() for counting and creating bitsets from enum values.

Filtering and Searching

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Feature {
    Auth,
    ApiV1,
    ApiV2,
    Dashboard,
    Reports,
    Admin,
}
 
impl Feature {
    fn is_api(&self) -> bool {
        matches!(self, Feature::ApiV1 | Feature::ApiV2)
    }
    
    fn requires_auth(&self) -> bool {
        matches!(self, Feature::ApiV1 | Feature::ApiV2 | Feature::Admin)
    }
}
 
fn main() {
    // Filter variants by predicate
    let api_features: Vec<Feature> = Feature::iter()
        .filter(|f| f.is_api())
        .collect();
    
    println!("API features: {:?}", api_features);
    
    // Find features requiring auth
    let auth_required: Vec<Feature> = Feature::iter()
        .filter(|f| f.requires_auth())
        .collect();
    
    println!("Auth required: {:?}", auth_required);
    
    // Check if any feature matches complex criteria
    let has_admin = Feature::iter().any(|f| {
        f.requires_auth() && !f.is_api()
    });
    println!("Has non-API auth feature: {}", has_admin);
}

Filter enum variants based on predicates.

Enum Variants with Data

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, PartialEq, EnumIter)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
 
fn main() {
    // iter() works with variants containing data
    // Note: Default values are used for struct/variant fields
    
    for msg in Message::iter() {
        match msg {
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move({}, {})", x, y),
            Message::Write(s) => println!("Write({})", s),
            Message::ChangeColor(r, g, b) => println!("Color({}, {}, {})", r, g, b),
        }
    }
    
    // Note: EnumIter with data variants requires Default trait
    // or uses Default::default() for each field
}

EnumIter works with variants containing data, using default values.

Memory and Performance

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Small {
    A,
    B,
    C,
}
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Larger {
    A, B, C, D, E, F, G, H, I, J,
}
 
fn main() {
    // iter() is lazy - doesn't allocate
    // Each variant is yielded on demand
    
    // Count is computed at compile time
    assert_eq!(Small::iter().count(), 3);
    assert_eq!(Larger::iter().count(), 10);
    
    // Iterator is size-hinted
    let mut iter = Small::iter();
    assert_eq!(iter.size_hint(), (3, Some(3)));
    
    // Zero-allocation iteration
    let count = Small::iter().fold(0, |acc, _| acc + 1);
    println!("Counted {} variants", count);
}

iter() produces a lazy iterator with no allocation overhead.

Manual Implementation Without Derive

// What the EnumIter derive macro generates:
 
use strum::IntoEnumIterator;
 
enum Color {
    Red,
    Green,
    Blue,
}
 
// Manual implementation (derive generates this)
impl IntoEnumIterator for Color {
    type Iterator = std::iter::Copied<std::slice::Iter<'static, Color>>;
    
    fn iter() -> Self::Iterator {
        static VARIANTS: [Color; 3] = [Color::Red, Color::Green, Color::Blue];
        VARIANTS.iter().copied()
    }
}
 
fn main() {
    // The derived implementation creates a static array
    // and iterates over it
    
    for color in Color::iter() {
        println!("{:?}", color);
    }
}
 
// Without strum, you would have to:
// 1. Manually maintain a static array
// 2. Remember to update it when adding variants
// 3. Implement the iterator yourself

The derive macro generates a static array of variants and iterates over it.

Comparing with Alternative Approaches

use strum::IntoEnumIterator;
 
// Approach 1: Manual listing (error-prone, maintenance burden)
fn manual_list() -> Vec<Status> {
    vec![Status::Pending, Status::InProgress, Status::Completed]
    // Easy to forget Status::Failed!
}
 
// Approach 2: strum::IntoEnumIterator (maintained automatically)
fn auto_list() -> impl Iterator<Item = Status> {
    Status::iter()
}
 
// Approach 3: match for exhaustiveness (compile-time checked)
fn exhaustiveness_check(s: Status) -> &'static str {
    match s {
        Status::Pending => "pending",
        Status::InProgress => "progress",
        Status::Completed => "done",
        Status::Failed => "error",
        // Compiler catches missing variants
    }
}
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Status {
    Pending,
    InProgress,
    Completed,
    Failed,
}
 
fn main() {
    // Manual approach can miss variants
    println!("Manual: {} variants", manual_list().len());
    
    // Auto approach always complete
    println!("Auto: {} variants", auto_list().count());
}

IntoEnumIterator eliminates the risk of forgetting to update variant lists.

Limitations and Constraints

use strum::IntoEnumIterator;
 
// Works: Simple enum
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Simple {
    A,
    B,
    C,
}
 
// Works: Enum with fields (uses Default)
#[derive(Debug, Clone, PartialEq, EnumIter)]
enum WithFields {
    Empty,
    WithInt(i32),
    WithString(String),
}
 
// Limitation: Cannot derive EnumIter for non-Clone types
// This would fail to compile:
// #[derive(EnumIter)]
// enum NonClone {
//     Variant(Box<i32>),
// }
 
// Limitation: Generic enums require bounds
#[derive(Debug, Clone, PartialEq, Eq, EnumIter)]
enum Generic<T: Clone + PartialEq> {
    Some(T),
    None,
}
 
fn main() {
    // WithFields uses Default::default() for variant fields
    for v in WithFields::iter() {
        println!("{:?}", v);
        // Outputs:
        // Empty
        // WithInt(0)
        // WithString("")
    }
}

EnumIter requires Clone, and variants with data use Default values.

Practical Use Case: State Machine

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum OrderState {
    Created,
    Paid,
    Shipped,
    Delivered,
    Cancelled,
    Refunded,
}
 
impl OrderState {
    fn transitions(&self) -> Vec<OrderState> {
        use OrderState::*;
        match self {
            Created => vec![Paid, Cancelled],
            Paid => vec![Shipped, Refunded],
            Shipped => vec![Delivered, Refunded],
            Delivered => vec![Refunded],
            Cancelled | Refunded => vec![],
        }
    }
    
    fn can_transition_to(&self, target: OrderState) -> bool {
        self.transitions().contains(&target)
    }
    
    fn valid_transitions(&self) -> Vec<OrderState> {
        OrderState::iter()
            .filter(|s| self.can_transition_to(*s))
            .collect()
    }
}
 
fn main() {
    let order = OrderState::Created;
    
    println!("From {:?}, can transition to:", order);
    for next in order.valid_transitions() {
        println!("  {:?}", next);
    }
    
    // All possible states
    println!("\nAll order states:");
    for state in OrderState::iter() {
        println!("  {:?}", state);
    }
}

Model state machines with automatic enumeration of all states.

Synthesis

Quick reference:

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum MyEnum {
    Variant1,
    Variant2,
    Variant3,
}
 
fn main() {
    // Basic iteration
    for variant in MyEnum::iter() {
        println!("{:?}", variant);
    }
    
    // Collect to Vec
    let all: Vec<MyEnum> = MyEnum::iter().collect();
    
    // Find by predicate
    let found = MyEnum::iter().find(|v| *v == MyEnum::Variant2);
    
    // Count variants
    let count = MyEnum::iter().count();
    
    // Filter
    let filtered: Vec<MyEnum> = MyEnum::iter()
        .filter(|v| matches!(v, MyEnum::Variant1 | MyEnum::Variant3))
        .collect();
    
    // Validate against all variants
    fn is_valid(v: MyEnum) -> bool {
        MyEnum::iter().any(|variant| variant == v)
    }
    
    // Transform to strings
    let names: Vec<String> = MyEnum::iter()
        .map(|v| format!("{:?}", v))
        .collect();
}

Key insight: IntoEnumIterator::iter bridges the gap between Rust's static type system and runtime enumeration needs. Without it, you'd need to manually maintain a list of variants or use a match statement—but match is for consuming values, not iterating. The EnumIter derive macro generates code that creates a static array of all variants, enabling iteration without reflection. This is invaluable for CLI tools (generate valid choices), configuration validation (check against all values), testing (cover all cases), documentation generation, and state machines (enumerate possible states). The iterator is lazy and zero-allocation, with size_hint optimized for the known variant count. Combine with other strum traits like Display, EnumString, and AsRefStr for complete enum handling from definition through serialization and display.