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:
- An iterator struct that tracks current position
next()implementation that matches on position- For each variant: constructs it with defaults or yields it directly
- 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.
