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