How does strum::IntoEnumIterator generate iterators for enums with associated data?

IntoEnumIterator generates an iterator that yields each variant of an enum, but for variants with associated data, it requires either default values (via #[strum(default)]) or the variant is skipped entirely. The macro cannot automatically generate meaningful associated data, so you must either provide defaults for each field or accept that variants with associated data won't be included in iteration.

Basic IntoEnumIterator Usage

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Direction {
    Up,
    Down,
    Left,
    Right,
}
 
fn basic_iteration() {
    // Iterate over all variants
    for direction in Direction::iter() {
        println!("{:?}", direction);
    }
    
    // Output: Up, Down, Left, Right
    
    // Collect into a vector
    let all_directions: Vec<Direction> = Direction::iter().collect();
    assert_eq!(all_directions.len(), 4);
}

For simple enums without data, IntoEnumIterator generates all variants in declaration order.

The Generated Code

// For the Direction enum above, strum generates:
 
impl strum::IntoEnumIterator for Direction {
    type Iterator = DirectionIter;
    
    fn iter() -> DirectionIter {
        DirectionIter {
            current: 0,
            count: 4,
        }
    }
}
 
struct DirectionIter {
    current: usize,
    count: usize,
}
 
impl Iterator for DirectionIter {
    type Item = Direction;
    
    fn next(&mut self) -> Option<Self::Item> {
        let item = match self.current {
            0 => Some(Direction::Up),
            1 => Some(Direction::Down),
            2 => Some(Direction::Left),
            3 => Some(Direction::Right),
            _ => None,
        };
        self.current += 1;
        item
    }
}

The macro generates an iterator struct that yields each variant in sequence.

Enums with Associated Data: The Problem

use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Status {
    Active,
    Inactive,
    Pending(String),  // Associated data!
}
 
fn problem_example() {
    // This compiles, but what should Pending(String) yield?
    // The macro can't know what String to use
    
    for status in Status::iter() {
        println!("{:?}", status);
    }
    // Output: Active, Inactive
    // Note: Pending is NOT included!
}

Variants with associated data are skipped by default because the macro cannot generate meaningful values for them.

Default Values for Associated Data

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Status {
    Active,
    Inactive,
    #[strum(default)]
    Pending(String),
}
 
fn with_defaults() {
    // Now Pending is included with default values
    for status in Status::iter() {
        println!("{:?}", status);
    }
    
    // Output: Active, Inactive, Pending("")
}

The #[strum(default)] attribute tells strum to use Default::default() for each field.

Custom Default Values

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Message {
    Start,
    Stop,
    #[strum(default)]
    Data { content: String, size: usize },
}
 
fn custom_defaults() {
    // Default::default() is used for each field
    for message in Message::iter() {
        println!("{:?}", message);
    }
    
    // Output:
    // Start
    // Stop
    // Data { content: "", size: 0 }
}

Each field uses its Default implementation when default is specified.

Using strum(default) with Default Trait

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, Default)]
struct Config {
    timeout: u64,
}
 
#[derive(Debug, EnumIter)]
enum Command {
    Run,
    Stop,
    #[strum(default)]
    Configure { config: Config },
}
 
fn with_struct_default() {
    for cmd in Command::iter() {
        println!("{:?}", cmd);
    }
    
    // Output:
    // Run
    // Stop
    // Configure { config: Config { timeout: 0 } }
}

Types with Default implementations work seamlessly with #[strum(default)].

Multiple Fields with Defaults

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Event {
    Click,
    #[strum(default)]
    KeyEvent { key: char, modifiers: u8 },
    #[strum(default)]
    MouseEvent { x: i32, y: i32, button: u8 },
}
 
fn multiple_fields() {
    for event in Event::iter() {
        println!("{:?}", event);
    }
    
    // Output:
    // Click
    // KeyEvent { key: '\0', modifiers: 0 }
    // MouseEvent { x: 0, y: 0, button: 0 }
}

All fields use their Default implementations.

Mixed Variants: Some With Data, Some Without

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Response {
    Success,
    Error(u16),         // Has data, no default - SKIPPED
    #[strum(default)]
    Warning(String),    // Has default - INCLUDED
    NotFound,           // No data - INCLUDED
}
 
fn mixed_variants() {
    let variants: Vec<Response> = Response::iter().collect();
    
    // Only Success, Warning, and NotFound
    // Error(u16) is skipped because it has no default
    
    assert_eq!(variants.len(), 3);
    // variants: [Success, Warning(""), NotFound]
}

Variants with data and no default attribute are silently skipped.

Tuple Variants with Multiple Fields

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Packet {
    Heartbeat,
    #[strum(default)]
    Data(String, Vec<u8>),  // Tuple variant with defaults
}
 
fn tuple_variants() {
    for packet in Packet::iter() {
        println!("{:?}", packet);
    }
    
    // Output:
    // Heartbeat
    // Data("", [])
}

Tuple variants use Default for each positional field.

Iteration Order

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Ordered {
    First,
    Second,
    Third,
    #[strum(default)]
    Fourth(i32),
}
 
fn iteration_order() {
    // Iterates in declaration order
    let items: Vec<Ordered> = Ordered::iter().collect();
    
    assert!(matches!(items[0], Ordered::First));
    assert!(matches!(items[1], Ordered::Second));
    assert!(matches!(items[2], Ordered::Third));
    assert!(matches!(items[3], Ordered::Fourth(0)));
}

Variants are yielded in the order they're declared in the enum.

What Happens When Default Is Missing

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter)]
enum Problematic {
    VariantA,
    VariantB(String),  // No default, will be skipped
    VariantC,
}
 
fn missing_default() {
    // This compiles and runs
    // But VariantB is silently excluded
    
    let count = Problematic::iter().count();
    assert_eq!(count, 2);  // Only VariantA and VariantC
    
    // The iterator yields VariantA, then VariantC
    // VariantB is not included
}

Variants without default and with associated data are silently excluded from iteration.

Checking What's Included

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter, PartialEq)]
enum Careful {
    Alpha,
    Beta(u32),
    Gamma,
}
 
fn check_inclusion() {
    // Variants without defaults are excluded
    
    // Verify what's in the iteration
    let all_variants: Vec<Careful> = Careful::iter().collect();
    
    // Beta(u32) is excluded because it has no default
    assert_eq!(all_variants.len(), 2);
    
    // Can check if specific values are in the iteration
    assert!(all_variants.contains(&Careful::Alpha));
    assert!(all_variants.contains(&Careful::Gamma));
    
    // Cannot check Beta(u32) directly because it needs a value
}

You cannot iterate over variants with data unless they have defaults.

Alternative: EnumDiscriminants for Discriminant Access

use strum_macros::EnumDiscriminants;
 
#[derive(Debug)]
enum Status {
    Active,
    Inactive,
    Pending(String),
}
 
// Generate a discriminant enum without the associated data
#[derive(Debug, EnumDiscriminants)]
#[strum_discriminants(derive(EnumIter))]
enum StatusDiscriminant {
    Active,
    Inactive,
    Pending,  // No associated data in discriminant
}
 
fn discriminant_iteration() {
    // Iterate over discriminants (variant names without data)
    for discriminant in StatusDiscriminant::iter() {
        println!("{:?}", discriminant);
    }
    
    // Output: Active, Inactive, Pending
    // All variants are included because discriminants don't have data
}

EnumDiscriminants creates a parallel enum without associated data, useful for iteration.

Combining IntoEnumIterator with Other Strum Features

use strum::IntoEnumIterator;
use strum_macros::{EnumIter, Display, EnumString};
 
#[derive(Debug, Clone, EnumIter, Display, EnumString)]
enum Color {
    #[strum(to_string = "red")]
    Red,
    #[strum(to_string = "green")]
    Green,
    #[strum(default, to_string = "blue")]
    Blue(u8),  // Brightness
}
 
fn combined_features() {
    // Iterate with defaults
    for color in Color::iter() {
        println!("{}: {}", color, color);
    }
    
    // Parse and check if it's in iteration
    let parsed: Color = "blue".parse().unwrap();
    
    // Can compare because Color derives Clone
    assert!(Color::iter().any(|c| format!("{}", c) == "blue"));
}

Combine EnumIter with other strum features for powerful enum handling.

Common Pattern: Validation Against All Variants

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
#[derive(Debug, EnumIter, PartialEq, Clone)]
enum Permission {
    Read,
    Write,
    Execute,
    #[strum(default)]
    Custom(String),
}
 
fn validate_permission(input: &str) -> bool {
    // Check if input matches any permission
    Permission::iter().any(|p| {
        let perm_str = format!("{:?}", p);
        perm_str.eq_ignore_ascii_case(input)
    })
}
 
fn permission_example() {
    assert!(validate_permission("Read"));
    assert!(validate_permission("WRITE"));
    assert!(validate_permission("Custom"));
}

IntoEnumIterator enables validation against all possible variants.

Common Pattern: Building Lookup Tables

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use std::collections::HashMap;
 
#[derive(Debug, EnumIter, Hash, Eq, PartialEq, Clone)]
enum Endpoint {
    Users,
    Products,
    #[strum(default)]
    Custom(String),
}
 
fn build_routes() -> HashMap<Endpoint, String> {
    // Initialize routes for all endpoints
    let mut routes = HashMap::new();
    
    for endpoint in Endpoint::iter() {
        let route = format!("/api/{:?}", endpoint).to_lowercase();
        routes.insert(endpoint, route);
    }
    
    routes
}

Use iteration to build maps, tables, or other data structures for all variants.

Limitations and Workarounds

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
// Limitation 1: Cannot specify non-default values
// All defaults must implement Default trait
 
#[derive(Debug, EnumIter)]
enum Limitation1 {
    Simple,
    // #[strum(default = "unknown")] // NOT SUPPORTED
    // WithDefault(String),
}
 
// Workaround: Use a custom type with Default
 
#[derive(Debug)]
struct NonEmptyString(String);
 
impl Default for NonEmptyString {
    fn default() -> Self {
        NonEmptyString("unknown".to_string())
    }
}
 
#[derive(Debug, EnumIter)]
enum Workaround1 {
    Simple,
    #[strum(default)]
    WithDefault(NonEmptyString),
}
 
// Limitation 2: All fields must implement Default
 
#[derive(Debug)]
struct NoDefault(String);  // No Default impl
 
#[derive(Debug, EnumIter)]
enum Limitation2 {
    Simple,
    // #[strum(default)]
    // WithField(NoDefault), // Won't compile - NoDefault has no Default
}

All fields in default variants must implement Default.

Manual Default Implementations

use strum::IntoEnumIterator;
use strum_macros::EnumIter;
 
// For types without Default, create a wrapper
 
struct Config {
    name: String,
    value: i32,
}
 
impl Default for Config {
    fn default() -> Self {
        Config {
            name: "default".to_string(),
            value: 0,
        }
    }
}
 
#[derive(Debug, EnumIter)]
enum Action {
    Start,
    Stop,
    #[strum(default)]
    Configure(Config),
}
 
fn custom_default() {
    for action in Action::iter() {
        println!("{:?}", action);
    }
    // Configure(Config { name: "default", value: 0 })
}

Implement Default for types used in default variants.

Synthesis

Key points:

Scenario Behavior
Variant without data Included in iteration
Variant with data, #[strum(default)] Included with Default::default()
Variant with data, no default Excluded from iteration
Tuple variant with default All fields use Default
Struct variant with default All fields use Default

What IntoEnumIterator generates:

  1. An iterator struct that tracks current position
  2. next() implementation that matches on position
  3. For each variant: constructs it with defaults or yields it directly
  4. Variants without defaults are skipped in generation

Key insight: IntoEnumIterator cannot magically create meaningful associated data for enum variants. When iterating over enums with associated data, you have two choices: use #[strum(default)] to include variants with their Default values, or accept that those variants will be excluded from iteration. For cases where you need to iterate over variant names without constructing values, use EnumDiscriminants to create a parallel enum without associated data. The generated iterator yields variants in declaration order, and all fields in default-marked variants must implement the Default trait.