Loading page…
Rust walkthroughs
Loading page…
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
// 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.
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.
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.
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.
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.