Loading page…
Rust walkthroughs
Loading page…
The strum crate provides derive macros that add useful functionality to Rust enums. Instead of manually implementing FromStr, Display, iteration, and other traits for your enums, strum generates this boilerplate automatically. The related strum_macros crate contains the derive implementations. Strum makes working with enums more ergonomic by providing string conversions, iteration over variants, property attachment, and more—all through simple derive attributes.
Key concepts:
&'static str# Cargo.toml
[dependencies]
strum = "0.26"
strum_macros = "0.26"use strum::IntoEnumIterator;
use strum_macros::{Display, EnumString, EnumIter};
#[derive(Debug, Display, EnumString, EnumIter)]
enum Color {
Red,
Green,
Blue,
}
fn main() {
// Display
println!("Color: {}", Color::Red); // "Color: Red"
// FromStr
let color: Color = "Green".parse().unwrap();
println!("Parsed: {:?}", color);
// Iterate
for color in Color::iter() {
println!("Variant: {}", color);
}
}use strum_macros::{Display, EnumString};
#[derive(Debug, Display, EnumString)]
enum Status {
Active,
Inactive,
Pending,
}
fn main() {
// Display trait - enum to string
let status = Status::Active;
println!("Status: {}", status); // "Status: Active"
let status_str = status.to_string();
println!("As string: {}", status_str);
// EnumString trait - string to enum
let parsed: Status = "Inactive".parse().unwrap();
println!("Parsed: {:?}", parsed);
// Handle parse errors
let result: Result<Status, _> = "Unknown".parse();
println!("Parse result: {:?}", result);
}use strum_macros::{Display, EnumString};
#[derive(Debug, Display, EnumString)]
enum Role {
#[strum(to_string = "admin")]
Admin,
#[strum(to_string = "moderator")]
Moderator,
#[strum(to_string = "regular_user")]
RegularUser,
}
fn main() {
let role = Role::Admin;
println!("Role: {}", role); // "Role: admin"
// Parse with custom string
let parsed: Role = "moderator".parse().unwrap();
println!("Parsed: {:?}", parsed);
// Case-sensitive parsing
let result: Result<Role, _> = "Admin".parse();
println!("Case sensitive: {:?}", result); // Err
}use strum_macros::EnumString;
#[derive(Debug, EnumString)]
enum HttpMethod {
#[strum(ascii_case_insensitive)]
Get,
#[strum(ascii_case_insensitive)]
Post,
#[strum(ascii_case_insensitive)]
Put,
#[strum(ascii_case_insensitive)]
Delete,
}
fn main() {
// All these work with case-insensitive parsing
let get1: HttpMethod = "GET".parse().unwrap();
let get2: HttpMethod = "get".parse().unwrap();
let get3: HttpMethod = "Get".parse().unwrap();
println!("All parsed: {:?}, {:?}, {:?}", get1, get2, get3);
}use strum::IntoEnumIterator;
use strum_macros::EnumIter;
#[derive(Debug, EnumIter)]
enum Direction {
North,
South,
East,
West,
}
fn main() {
// Iterate over all variants
println!("All directions:");
for dir in Direction::iter() {
println!(" - {:?}", dir);
}
// Collect into vector
let all_dirs: Vec<Direction> = Direction::iter().collect();
println!("Count: {}", all_dirs.len());
// Use with iterator methods
let count = Direction::iter().count();
println!("Total variants: {}", count);
}use strum_macros::IntoStaticStr;
#[derive(Debug, IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
enum EventType {
UserCreated,
UserDeleted,
OrderPlaced,
OrderShipped,
}
fn main() {
// Convert to &'static str
let event: EventType = EventType::UserCreated;
let name: &'static str = event.into();
println!("Event name: {}", name); // "user_created"
// Use in static contexts
fn get_event_name(event: EventType) -> &'static str {
event.into()
}
println!("Name: {}", get_event_name(EventType::OrderPlaced));
}use strum_macros::{Display, EnumString};
#[derive(Debug, Display, EnumString)]
#[strum(serialize_all = "snake_case")]
enum ErrorCode {
InvalidInput,
NetworkError,
DatabaseFailure,
AuthenticationFailed,
}
fn main() {
// All variants use snake_case
let error = ErrorCode::InvalidInput;
println!("Error: {}", error); // "Error: invalid_input"
// Parse with snake_case
let parsed: ErrorCode = "network_error".parse().unwrap();
println!("Parsed: {:?}", parsed);
// Available serialize_all options:
// - "lowercase"
// - "UPPERCASE"
// - "snake_case"
// - "kebab-case"
// - "camelCase"
// - "PascalCase"
// - "SCREAMING_SNAKE_CASE"
}use strum_macros::EnumVariantNames;
#[derive(Debug, EnumVariantNames)]
enum Animal {
Dog,
Cat,
Bird,
Fish,
}
fn main() {
// Get all variant names as a slice
let names: &[&str] = Animal::VARIANTS;
println!("Variant names: {:?}", names);
// Use in validation
fn is_valid_animal(name: &str) -> bool {
Animal::VARIANTS.contains(&name)
}
println!("Dog valid: {}", is_valid_animal("Dog"));
println!("Cow valid: {}", is_valid_animal("Cow"));
}use strum_macros::EnumCount;
#[derive(Debug, EnumCount)]
enum Priority {
Low,
Medium,
High,
Critical,
}
fn main() {
// Get the count at compile time
println!("Priority count: {}", Priority::COUNT);
// Useful for array sizing
let priority_counts = [0u32; Priority::COUNT];
println!("Array length: {}", priority_counts.len());
}use strum::FromRepr;
use strum_macros::FromRepr;
#[derive(Debug, FromRepr)]
#[repr(u8)]
enum Command {
Start = 1,
Stop = 2,
Pause = 3,
Resume = 4,
}
fn main() {
// Convert from discriminant
let cmd = Command::from_repr(1);
println!("Command 1: {:?}", cmd); // Some(Start)
let invalid = Command::from_repr(99);
println!("Command 99: {:?}", invalid); // None
// Useful for parsing binary protocols
fn parse_command(byte: u8) -> Option<Command> {
Command::from_repr(byte)
}
}use strum_macros::EnumMessage;
#[derive(Debug, EnumMessage)]
enum LogLevel {
#[strum(message = "Detailed debug information")]
Debug,
#[strum(message = "General information")]
Info,
#[strum(message = "Warning about potential issues")]
Warning,
#[strum(message = "Error that needs attention")]
Error,
}
fn main() {
let level = LogLevel::Warning;
if let Some(msg) = level.get_message() {
println!("Level: {:?} - {}", level, msg);
}
// Iterate with messages
use strum::IntoEnumIterator;
#[derive(EnumIter, EnumMessage)]
enum Priority {
#[strum(message = "Low priority")]
Low,
#[strum(message = "High priority")]
High,
}
for priority in Priority::iter() {
println!("{:?}: {}", priority, priority.get_message().unwrap());
}
}use strum_macros::EnumMessage;
#[derive(Debug, EnumMessage)]
enum ConfigOption {
#[strum(
message = "Server port",
detailed_message = "The TCP port the server will listen on. Default: 8080"
)]
Port,
#[strum(
message = "Database URL",
detailed_message = "Connection string for the database. Required for production."
)]
DatabaseUrl,
#[strum(
message = "Log level",
detailed_message = "Verbosity of logging: debug, info, warn, error"
)]
LogLevel,
}
fn main() {
let opt = ConfigOption::Port;
println!("Short: {}", opt.get_message().unwrap());
println!("Detailed: {}", opt.get_detailed_message().unwrap());
}use strum_macros::EnumProperty;
#[derive(Debug, EnumProperty)]
enum HttpStatus {
#[strum(props(code = "200", description = "OK"))]
Ok,
#[strum(props(code = "201", description = "Created"))]
Created,
#[strum(props(code = "400", description = "Bad Request"))]
BadRequest,
#[strum(props(code = "404", description = "Not Found"))]
NotFound,
#[strum(props(code = "500", description = "Internal Server Error"))]
InternalError,
}
fn main() {
let status = HttpStatus::NotFound;
println!("Code: {:?}", status.get_str("code"));
println!("Description: {:?}", status.get_str("description"));
// Convert to integer
fn get_status_code(status: HttpStatus) -> u16 {
status.get_str("code")
.and_then(|s| s.parse().ok())
.unwrap()
}
println!("Numeric code: {}", get_status_code(HttpStatus::Ok));
}use strum::IntoEnumIterator;
use strum_macros::{Display, EnumString, EnumIter, EnumMessage, EnumProperty};
#[derive(Debug, Clone, Display, EnumString, EnumIter, EnumMessage, EnumProperty)]
#[strum(serialize_all = "kebab-case")]
enum Feature {
#[strum(message = "User authentication", props(priority = "high"))]
Authentication,
#[strum(message = "Data export", props(priority = "medium"))]
Export,
#[strum(message = "Real-time notifications", props(priority = "low"))]
Notifications,
}
fn main() {
// Display
println!("Feature: {}", Feature::Authentication);
// Parse
let feature: Feature = "export".parse().unwrap();
println!("Parsed: {:?}", feature);
// Iterate with messages and properties
for f in Feature::iter() {
println!(
"{}: {} (priority: {:?})",
f,
f.get_message().unwrap(),
f.get_str("priority")
);
}
}use strum_macros::{Display, EnumString};
#[derive(Debug, Display, EnumString)]
enum Result<T, E> {
Ok(T),
Err(E),
}
// Better approach: use variants with specific meanings
#[derive(Debug, Display, EnumString)]
enum Status {
#[strum(to_string = "success")]
Success,
#[strum(to_string = "failure")]
Failure,
#[strum(to_string = "pending")]
Pending,
}
fn main() {
let status = Status::Success;
println!("Status: {}", status);
}use strum::IntoEnumIterator;
use strum_macros::{Display, EnumIter, EnumString};
#[derive(Debug, Display, EnumIter, EnumString)]
enum Config {
Host,
Port,
#[strum(disabled)]
Internal, // Won't be included in iter() or parsing
}
fn main() {
// Internal is skipped in iteration
for config in Config::iter() {
println!("Config: {}", config);
}
// Internal cannot be parsed
let result: Result<Config, _> = "Internal".parse();
println!("Parse Internal: {:?}", result); // Err
}use strum_macros::EnumString;
#[derive(Debug, EnumString)]
enum Color {
Red,
Green,
Blue,
#[strum(default)]
Unknown(String), // Catches any unrecognized value
}
fn main() {
let known: Color = "Red".parse().unwrap();
println!("Known: {:?}", known);
let unknown: Color = "Purple".parse().unwrap();
println!("Unknown: {:?}", unknown); // Unknown("Purple")
}use strum_macros::{Display, EnumString};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Display, EnumString, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
enum Action {
CreateUser,
DeleteUser,
UpdateUser,
ListUsers,
}
fn main() {
let action = Action::CreateUser;
// Serde JSON serialization
let json = serde_json::to_string(&action).unwrap();
println!("JSON: {}", json); // "create_user"
// Display
println!("Display: {}", action); // "create_user"
// Parse
let parsed: Action = "delete_user".parse().unwrap();
println!("Parsed: {:?}", parsed);
}use strum::IntoEnumIterator;
use strum_macros::{Display, EnumString, EnumIter};
#[derive(Debug, Clone, Display, EnumString, EnumIter)]
enum OutputFormat {
Json,
Yaml,
Toml,
Text,
}
impl OutputFormat {
fn extensions(&self) -> &'static str {
match self {
OutputFormat::Json => ".json",
OutputFormat::Yaml => ".yaml,.yml",
OutputFormat::Toml => ".toml",
OutputFormat::Text => ".txt",
}
}
}
fn main() {
// List available formats
println!("Available formats:");
for format in OutputFormat::iter() {
println!(" {} ({})", format, format.extensions());
}
// Parse user input
let user_choice = "yaml";
let format: OutputFormat = user_choice.parse().unwrap();
println!("Selected: {}", format);
}use strum::IntoEnumIterator;
use strum_macros::{Display, EnumIter, EnumMessage};
#[derive(Debug, Clone, Display, EnumIter, EnumMessage)]
enum LogLevel {
#[strum(message = "Off - No logging")]
Off,
#[strum(message = "Error - Only errors")]
Error,
#[strum(message = "Warn - Warnings and errors")]
Warn,
#[strum(message = "Info - General information")]
Info,
#[strum(message = "Debug - Detailed debugging")]
Debug,
#[strum(message = "Trace - Everything")]
Trace,
}
fn print_log_levels() {
println!("Log levels:");
for level in LogLevel::iter() {
println!(" {:6} - {}", level, level.get_message().unwrap());
}
}
fn main() {
print_log_levels();
}use strum_macros::{Display, EnumString, EnumVariantNames};
#[derive(Debug, Display, EnumString, EnumVariantNames)]
enum Country {
US,
UK,
CA,
DE,
FR,
JP,
AU,
}
fn validate_country(code: &str) -> Result<Country, String> {
code.parse::<Country>()
.map_err(|_| {
format!(
"Invalid country code '{}'. Valid codes: {:?}",
code,
Country::VARIANTS
)
})
}
fn main() {
match validate_country("DE") {
Ok(country) => println!("Valid: {}", country),
Err(e) => println!("Error: {}", e),
}
match validate_country("XX") {
Ok(country) => println!("Valid: {}", country),
Err(e) => println!("Error: {}", e),
}
}use strum::IntoEnumIterator;
use strum_macros::{Display, EnumIter, EnumString};
#[derive(Debug, Clone, Display, EnumIter, EnumString)]
#[strum(serialize_all = "kebab-case")]
enum Endpoint {
#[strum(to_string = "users")]
Users,
#[strum(to_string = "users/{id}")]
UserById,
#[strum(to_string = "posts")]
Posts,
#[strum(to_string = "posts/{id}/comments")]
PostComments,
#[strum(to_string = "auth/login")]
Login,
}
impl Endpoint {
fn full_path(&self) -> String {
format!("/api/v1/{}", self)
}
}
fn main() {
println!("Available endpoints:");
for endpoint in Endpoint::iter() {
println!(" {}", endpoint.full_path());
}
let endpoint: Endpoint = "posts".parse().unwrap();
println!("Selected: {}", endpoint.full_path());
}use strum::IntoEnumIterator;
use strum_macros::{Display, EnumIter, FromRepr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumIter, FromRepr)]
#[repr(u8)]
enum State {
#[strum(to_string = "idle")]
Idle = 0,
#[strum(to_string = "connecting")]
Connecting = 1,
#[strum(to_string = "connected")]
Connected = 2,
#[strum(to_string = "disconnecting")]
Disconnecting = 3,
#[strum(to_string = "error")]
Error = 99,
}
impl State {
fn transitions(&self) -> Vec<State> {
use State::*;
match self {
Idle => vec![Connecting],
Connecting => vec![Connected, Error],
Connected => vec![Disconnecting],
Disconnecting => vec![Idle],
Error => vec![Idle],
}
}
fn can_transition_to(&self, target: State) -> bool {
self.transitions().contains(&target)
}
}
fn main() {
println!("State: {}", State::Idle);
// All states
for state in State::iter() {
println!("State {} can transition to: {:?}",
state,
state.transitions()
);
}
// From repr
let state = State::from_repr(2);
println!("State 2: {:?}", state);
}Display derives ToString for enum variantsEnumString derives FromStr for parsing strings to enumsEnumIter enables iteration over all variants via iter()IntoStaticStr converts to &'static strEnumVariantNames provides VARIANTS constant with all namesEnumCount provides COUNT constant with variant countFromRepr converts discriminants to enum variantsEnumMessage adds get_message() and get_detailed_message()EnumProperty adds get_str() for custom properties#[strum(to_string = "...")] for custom serialization#[strum(serialize_all = "...")] for case transformation#[strum(disabled)] to skip variants#[strum(default)] for catch-all variantstrum crate contains traits, strum_macros contains derives