What is the purpose of strum::IntoEnumIterator::iter for compile-time enum traversal?

strum::IntoEnumIterator::iter provides a way to iterate over all variants of an enum at runtime, generating an iterator that yields each variant in declaration order. This is achieved through the EnumIter derive macro, which generates code at compile time that produces an iterator over all enum variants without requiring the enum variants to hold data—variants with fields are skipped or require special handling. The purpose is to enable patterns like building lookup tables from enum variants, validating that all cases are handled in UI displays or CLI arguments, generating documentation or help text automatically, and creating exhaustive mappings between enum variants and other values. Unlike runtime reflection, strum's approach generates all the iteration code at compile time, producing zero-cost abstractions that the compiler can optimize while still providing the ergonomics of dynamic enumeration.

Basic Enum Iteration

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, EnumIter)]
enum Direction {
    North,
    South,
    East,
    West,
}
 
fn main() {
    // Iterate over all enum variants
    for direction in Direction::iter() {
        println!("{:?}", direction);
    }
    
    // Collect into a vector
    let all_directions: Vec<Direction> = Direction::iter().collect();
    assert_eq!(all_directions.len(), 4);
    
    // Works with iterator methods
    let direction_names: Vec<String> = Direction::iter()
        .map(|d| format!("{:?}", d))
        .collect();
}

IntoEnumIterator::iter() returns an iterator over all enum variants.

The EnumIter Derive Macro

use strum::IntoEnumIterator;
 
// The #[derive(EnumIter)] macro generates:
// 1. An iterator type for the enum
// 2. Implementation of IntoEnumIterator trait
// 3. The iter() method that returns the iterator
 
#[derive(Debug, EnumIter)]
enum Status {
    Pending,
    InProgress,
    Completed,
    Failed,
}
 
// What the macro generates (simplified):
// impl IntoEnumIterator for Status {
//     type Iterator = StatusIter;
//     fn iter() -> StatusIter { ... }
// }
//
// And an iterator type that yields each variant
 
fn main() {
    // The iterator yields variants in declaration order
    let statuses: Vec<Status> = Status::iter().collect();
    
    assert_eq!(statuses[0], Status::Pending);
    assert_eq!(statuses[1], Status::InProgress);
    assert_eq!(statuses[2], Status::Completed);
    assert_eq!(statuses[3], Status::Failed);
}

The derive macro generates all the boilerplate for iteration at compile time.

Handling Enums with Data

use strum::IntoEnumIterator;
 
// EnumIter works differently with variants that have data
 
#[derive(Debug, EnumIter)]
enum Simple {
    A,
    B,
    C,
}
 
// This works fine - all variants are unit variants
for variant in Simple::iter() {
    println!("{:?}", variant);
}
 
// For enums with data, the behavior depends on the variant
#[derive(Debug, EnumIter)]
enum WithData {
    UnitVariant,           // Included in iteration
    TupleVariant(i32),     // Included but requires special handling
    StructVariant { x: i32, y: i32 },  // Included
}
 
fn main() {
    // When iterating, variants with fields use Default values
    // or require you to specify values
    
    for variant in WithData::iter() {
        println!("{:?}", variant);
    }
    // Prints:
    // UnitVariant
    // TupleVariant(0)  <- uses Default::default()
    // StructVariant { x: 0, y: 0 }
}

Variants with fields are included in iteration using their Default values.

Custom Default Values for Variants

use strum::IntoEnumIterator;
use strum::EnumIter;
 
// You can specify default values for variants with data
 
#[derive(Debug, EnumIter)]
enum Color {
    Rgb { r: u8, g: u8, b: u8 },
    Hsv { h: u16, s: u8, v: u8 },
}
 
// By default, these use Default values
// But you can implement Default to control this
 
impl Default for Color {
    fn default() -> Self {
        Color::Rgb { r: 0, g: 0, b: 0 }
    }
}
 
fn main() {
    for color in Color::iter() {
        println!("{:?}", color);
    }
}

The Default trait controls what values variants with fields use during iteration.

Building Lookup Tables from Enums

use strum::IntoEnumIterator;
use std::collections::HashMap;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
enum HttpStatus {
    Ok,
    NotFound,
    InternalServerError,
    BadRequest,
}
 
impl HttpStatus {
    fn code(&self) -> u16 {
        match self {
            HttpStatus::Ok => 200,
            HttpStatus::NotFound => 404,
            HttpStatus::InternalServerError => 500,
            HttpStatus::BadRequest => 400,
        }
    }
    
    fn message(&self) -> &'static str {
        match self {
            HttpStatus::Ok => "Success",
            HttpStatus::NotFound => "Not Found",
            HttpStatus::InternalServerError => "Internal Server Error",
            HttpStatus::BadRequest => "Bad Request",
        }
    }
}
 
fn main() {
    // Build a lookup table from enum variants
    let code_to_status: HashMap<u16, HttpStatus> = HttpStatus::iter()
        .map(|status| (status.code(), status))
        .collect();
    
    let status_to_message: HashMap<HttpStatus, &'static str> = HttpStatus::iter()
        .map(|status| (status, status.message()))
        .collect();
    
    // Use the lookup tables
    let status = code_to_status.get(&404).unwrap();
    println!("{}: {}", status, status_to_message[status]);
}

Enum iteration enables building exhaustive mappings between variants and associated data.

Validating Exhaustive Handling

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
enum Permission {
    Read,
    Write,
    Delete,
    Admin,
}
 
fn permission_name(perm: Permission) -> &'static str {
    match perm {
        Permission::Read => "read",
        Permission::Write => "write",
        Permission::Delete => "delete",
        Permission::Admin => "admin",
    }
}
 
fn validate_permission_handling() {
    // Ensure all permissions are handled
    for perm in Permission::iter() {
        let _name = permission_name(perm);
        // If we add a new variant without updating the match,
        // the compiler catches it (match is exhaustive)
        // But we can also use this for runtime validation
    }
}
 
fn main() {
    validate_permission_handling();
    
    // Check that all permissions have a display name
    let all_have_names = Permission::iter()
        .all(|p| !permission_name(p).is_empty());
    
    assert!(all_have_names);
}

Enum iteration helps validate that all variants are properly handled in match expressions.

CLI Argument Generation

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, EnumIter)]
enum OutputFormat {
    Json,
    Yaml,
    Csv,
    Table,
}
 
impl OutputFormat {
    fn extension(&self) -> &'static str {
        match self {
            OutputFormat::Json => "json",
            OutputFormat::Yaml => "yaml",
            OutputFormat::Csv => "csv",
            OutputFormat::Table => "txt",
        }
    }
}
 
fn generate_help_text() -> String {
    let formats: Vec<String> = OutputFormat::iter()
        .map(|f| format!("{:?} (.{})", f, f.extension()))
        .collect();
    
    format!("Available formats: {}", formats.join(", "))
}
 
fn main() {
    println!("{}", generate_help_text());
    // Available formats: Json (.json), Yaml (.yaml), Csv (.csv), Table (.txt)
    
    // Parse CLI argument
    let available_formats: Vec<&'static str> = OutputFormat::iter()
        .map(|f| f.extension())
        .collect();
    
    // Validate user input
    let user_format = "json";
    if available_formats.contains(&user_format) {
        println!("Valid format");
    }
}

Enum iteration enables dynamic help text generation and validation.

UI Display from Enums

use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, Copy, EnumIter)]
enum Tab {
    Home,
    Settings,
    Profile,
    About,
}
 
impl Tab {
    fn label(&self) -> &'static str {
        match self {
            Tab::Home => "Home",
            Tab::Settings => "Settings",
            Tab::Profile => "User Profile",
            Tab::About => "About Us",
        }
    }
    
    fn icon(&self) -> &'static str {
        match self {
            Tab::Home => "🏠",
            Tab::Settings => "⚙️",
            Tab::Profile => "👤",
            Tab::About => "ℹ️",
        }
    }
}
 
fn render_tabs() -> Vec<(Tab, String, String)> {
    // Generate UI data for all tabs
    Tab::iter()
        .map(|tab| (tab, tab.label().to_string(), tab.icon().to_string()))
        .collect()
}
 
fn main() {
    let tabs = render_tabs();
    for (tab, label, icon) in tabs {
        println!("{} {} - {:?}", icon, label, tab);
    }
}

Enum iteration generates UI elements from enum definitions.

Serialization and Deserialization Support

use strum::IntoEnumIterator;
use serde::{Serialize, Deserialize};
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Environment {
    Development,
    Staging,
    Production,
}
 
fn main() {
    // Generate all valid values for validation
    let valid_environments: Vec<String> = Environment::iter()
        .map(|e| serde_json::to_string(&e).unwrap().trim_matches('"').to_string())
        .collect();
    
    println!("Valid environments: {:?}", valid_environments);
    // Valid environments: ["development", "staging", "production"]
    
    // Validate configuration input
    let config_env = "production";
    let valid = Environment::iter()
        .any(|e| serde_json::to_string(&e).unwrap().trim_matches('"') == config_env);
    
    assert!(valid);
}

Enum iteration helps validate serialized values against all valid enum variants.

Comparison with Other Approaches

// Option 1: Manual iteration
enum Manual {
    A, B, C,
}
 
impl Manual {
    const ALL: [Manual; 3] = [Manual::A, Manual::B, Manual::C];
    
    fn iter() -> impl Iterator<Item = Manual> {
        Self::ALL.iter().copied()
    }
}
 
// Option 2: strum's EnumIter
use strum::IntoEnumIterator;
 
#[derive(EnumIter)]
enum WithStrum {
    A, B, C,
}
 
// strum's advantages:
// 1. No manual maintenance of ALL array
// 2. Automatically stays in sync with enum definition
// 3. Less boilerplate code
// 4. Compile-time generation
 
fn main() {
    // Both work, but strum is more maintainable
    for m in Manual::iter() {
        // ...
    }
    
    for s in WithStrum::iter() {
        // ...
    }
    
    // If you add a variant to Manual, you must update ALL
    // If you add a variant to WithStrum, EnumIter handles it
}

EnumIter is more maintainable than manually maintaining a list of all variants.

Performance Characteristics

use strum::IntoEnumIterator;
 
#[derive(Debug, EnumIter)]
enum Small {
    A, B, C,
}
 
#[derive(Debug, EnumIter)]
enum Large {
    V0, V1, V2, V3, V4, V5, V6, V7, V8, V9,
    V10, V11, V12, V13, V14, V15, V16, V17, V18, V19,
    // ... hundreds more
}
 
fn main() {
    // EnumIter is a zero-cost abstraction
    // The iterator is generated at compile time
    // Iteration is O(n) where n is number of variants
    
    // For small enums, iteration is very fast
    let small_count = Small::iter().count();
    
    // For large enums, still efficient
    // The iterator yields one variant at a time
    // No heap allocation required
    
    // The iterator type is fixed-size (uses an index)
    // It doesn't store all variants at once
    
    println!("Small enum has {} variants", small_count);
}

EnumIter is a compile-time, zero-cost abstraction with O(n) iteration.

Skip Variants from Iteration

use strum::IntoEnumIterator;
 
#[derive(Debug, EnumIter)]
enum Feature {
    Basic,
    Advanced,
    #[strum(skip)]
    Internal,  // Not included in iteration
    Experimental,
}
 
fn main() {
    // Internal is skipped
    let features: Vec<Feature> = Feature::iter().collect();
    
    // Only Basic, Advanced, Experimental
    assert_eq!(features.len(), 3);
    
    for feature in Feature::iter() {
        println!("{:?}", feature);
    }
    // Prints: Basic, Advanced, Experimental
    // Internal is not printed
}

Use #[strum(skip)] to exclude variants from iteration.

Practical Example: Configuration Validation

use strum::IntoEnumIterator;
use std::collections::HashSet;
 
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
enum LogLevel {
    Trace,
    Debug,
    Info,
    Warn,
    Error,
}
 
impl LogLevel {
    fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "trace" => Some(LogLevel::Trace),
            "debug" => Some(LogLevel::Debug),
            "info" => Some(LogLevel::Info),
            "warn" => Some(LogLevel::Warn),
            "error" => Some(LogLevel::Error),
            _ => None,
        }
    }
    
    fn valid_values() -> Vec<&'static str> {
        vec!["trace", "debug", "info", "warn", "error"]
    }
}
 
fn validate_config(config_log_levels: &[String]) -> Result<(), String> {
    let valid_levels: HashSet<String> = LogLevel::iter()
        .map(|l| format!("{:?}", l).to_lowercase())
        .collect();
    
    for level in config_log_levels {
        if !valid_levels.contains(&level.to_lowercase()) {
            return Err(format!("Invalid log level: {}. Valid levels: {:?}", 
                level, LogLevel::valid_values()));
        }
    }
    
    Ok(())
}
 
fn main() {
    let config = vec!["info".to_string(), "debug".to_string()];
    assert!(validate_config(&config).is_ok());
    
    let invalid_config = vec!["info".to_string(), "verbose".to_string()];
    assert!(validate_config(&invalid_config).is_err());
}

Enum iteration enables comprehensive configuration validation.

Synthesis

When to use IntoEnumIterator::iter:

Use Case Benefit
Building lookup tables Ensure all variants are mapped
Generating help text Stay in sync with enum definition
Validation Check against all valid values
UI rendering Display all options dynamically
Configuration Parse and validate user input
Testing Test all enum variants

Key characteristics:

Property Description
Compile-time Code generated by macro, no runtime reflection
Zero-cost No heap allocation, efficient iteration
Declaration order Variants yielded in definition order
Exhaustive All variants included (unless skipped)
Derive-based Requires #[derive(EnumIter)]

Comparison with alternatives:

Approach Maintenance Compile-time safety
Manual ALL array Manual sync needed Same
strum::EnumIter Automatic sync Same
Runtime reflection Automatic Different (no type safety)

Key insight: IntoEnumIterator::iter solves the problem of "how do I iterate over all enum variants?" without requiring runtime reflection or manually maintained constant arrays. The derive macro generates iterator code at compile time, which means the compiler can optimize it fully and the iteration is type-safe. This pattern is particularly valuable when you need to ensure that code handles all enum variants: by iterating over the enum and applying logic to each variant, you can validate at runtime that your handling is exhaustive, while the match expressions inside your code ensure compile-time exhaustiveness. The combination means both adding a new variant (caught by match exhaustiveness) and forgetting to update related logic (caught by runtime iteration validation) are detected. strum's approach embodies a Rust philosophy: use the type system and compile-time code generation to achieve ergonomics without sacrificing performance or safety.