Loading page…
Rust walkthroughs
Loading page…
strum::Display derive generate human-readable enum variant strings without manual implementation?strum::Display generates a Display trait implementation that converts enum variant names to strings by applying configurable transformations—lowercasing, replacing underscores with spaces, or using custom attributes—eliminating the need for manual match statements or string formatting code. The derive macro inspects variant names at compile time and produces optimized code that returns static strings or formatted output, with customization through #[strum(...)] attributes for casing, prefix stripping, and per-variant overrides.
use strum::Display;
#[derive(Display)]
enum Status {
Active,
Inactive,
Pending,
Complete,
}
fn main() {
let status = Status::Active;
println!("Status: {}", status); // Output: "Active"
let status = Status::Pending;
println!("Status: {}", status); // Output: "Pending"
// The generated Display impl:
// impl std::fmt::Display for Status {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// match self {
// Status::Active => write!(f, "Active"),
// Status::Inactive => write!(f, "Inactive"),
// Status::Pending => write!(f, "Pending"),
// Status::Complete => write!(f, "Complete"),
// }
// }
// }
}The basic derive generates a match that writes the variant name as-is.
use strum::Display;
#[derive(Display)]
#[strum(serialize_all = "lowercase")]
enum Status {
Active,
Inactive,
Pending,
Complete,
}
fn main() {
println!("{}", Status::Active); // "active"
println!("{}", Status::Inactive); // "inactive"
println!("{}", Status::Pending); // "pending"
}
#[derive(Display)]
#[strum(serialize_all = "snake_case")]
enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
}
fn main() {
// Already simple names, but let's see more complex examples
println!("{}", HttpMethod::Get); // "get"
}
#[derive(Display)]
#[strum(serialize_all = "kebab_case")]
enum ContentType {
ApplicationJson,
TextHtml,
MultipartFormData,
}
fn main() {
println!("{}", ContentType::ApplicationJson); // "application-json"
println!("{}", ContentType::TextHtml); // "text-html"
println!("{}", ContentType::MultipartFormData); // "multipart-form-data"
}
#[derive(Display)]
#[strum(serialize_all = "screaming_snake_case")]
enum LogLevel {
Debug,
Info,
Warning,
Error,
}
fn main() {
println!("{}", LogLevel::Debug); // "DEBUG"
println!("{}", LogLevel::Info); // "INFO"
println!("{}", LogLevel::Warning); // "WARNING"
}
#[derive(Display)]
#[strum(serialize_all = "title_case")]
enum Priority {
LowPriority,
MediumPriority,
HighPriority,
}
fn main() {
println!("{}", Priority::LowPriority); // "Low Priority"
println!("{}", Priority::MediumPriority); // "Medium Priority"
}serialize_all applies casing rules to all variants uniformly.
use strum::Display;
// All available casing options:
#[derive(Display)]
#[strum(serialize_all = "lowercase")]
// "SomeVariant" -> "somevariant"
struct LowercaseExample;
#[derive(Display)]
#[strum(serialize_all = "UPPERCASE")]
// "SomeVariant" -> "SOMEVARIANT"
struct UppercaseExample;
#[derive(Display)]
#[strum(serialize_all = "snake_case")]
// "SomeVariant" -> "some_variant"
struct SnakeCaseExample;
#[derive(Display)]
#[strum(serialize_all = "kebab_case")]
// "SomeVariant" -> "some-variant"
struct KebabCaseExample;
#[derive(Display)]
#[strum(serialize_all = "screaming_snake_case")]
// "SomeVariant" -> "SOME_VARIANT"
struct ScreamingSnakeCaseExample;
#[derive(Display)]
#[strum(serialize_all = "title_case")]
// "SomeVariant" -> "Some Variant"
struct TitleCaseExample;
#[derive(Display)]
#[strum(serialize_all = "camelCase")]
// "SomeVariant" -> "someVariant"
struct CamelCaseExample;
#[derive(Display)]
#[strum(serialize_all = "PascalCase")]
// "SomeVariant" -> "SomeVariant" (no change)
struct PascalCaseExample;Casing options cover common string format conventions.
use strum::Display;
#[derive(Display)]
enum HttpResponse {
#[strum(serialize = "OK")]
Ok,
#[strum(serialize = "Not Found")]
NotFound,
#[strum(serialize = "Internal Server Error")]
InternalServerError,
// Without attribute, uses default behavior
BadRequest,
}
fn main() {
println!("{}", HttpResponse::Ok); // "OK"
println!("{}", HttpResponse::NotFound); // "Not Found"
println!("{}", HttpResponse::InternalServerError); // "Internal Server Error"
println!("{}", HttpResponse::BadRequest); // "BadRequest"
}
// Multiple serialization options (for IntoEnumIterator)
#[derive(Display)]
enum Color {
#[strum(serialize = "red", serialize = "Red")]
Red,
#[strum(serialize = "blue", serialize = "Blue")]
Blue,
}
fn main() {
// Display uses the first serialize value
println!("{}", Color::Red); // "red"
}Use #[strum(serialize = "...")] to override individual variant strings.
use strum::Display;
#[derive(Display)]
#[strum(serialize_all = "snake_case")]
enum DatabaseError {
ConnectionFailed, // -> "connection_failed"
QueryTimeout, // -> "query_timeout"
#[strum(serialize = "db_not_found")]
DatabaseNotFound, // -> "db_not_found" (override)
PermissionDenied, // -> "permission_denied"
#[strum(serialize = "unknown")]
Unknown, // -> "unknown" (override)
}
fn main() {
println!("{}", DatabaseError::ConnectionFailed); // "connection_failed"
println!("{}", DatabaseError::DatabaseNotFound); // "db_not_found"
println!("{}", DatabaseError::PermissionDenied); // "permission_denied"
println!("{}", DatabaseError::Unknown); // "unknown"
}Override specific variants while keeping the default casing for others.
use strum::Display;
#[derive(Display)]
enum Message {
Simple(String),
Complex { code: u32, text: String },
Empty,
}
fn main() {
// For variants with fields, strum uses the variant name
// Fields are NOT included in the Display output by default
let msg = Message::Simple("hello".to_string());
println!("{}", msg); // "Simple"
let msg = Message::Complex {
code: 404,
text: "Not Found".to_string()
};
println!("{}", msg); // "Complex"
let msg = Message::Empty;
println!("{}", msg); // "Empty"
}
// To include field data, you need a custom impl or different approach:
#[derive(Display)]
enum ResultCode {
#[strum(serialize = "Success")]
Success,
#[strum(serialize = "Error")]
Error { code: u32 },
// Still displays as "Error" - fields ignored by default
}
// For field-aware Display, implement manually:
impl std::fmt::Display for ResultCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResultCode::Success => write!(f, "Success"),
ResultCode::Error { code } => write!(f, "Error(code: {})", code),
}
}
}Display derive uses variant names; associated data isn't included in output.
use strum::Display;
#[derive(Display)]
#[strum(serialize_all = "lowercase")]
#[strum(prefix = "status_")] // Not a real attribute, showing concept
enum Status {
StatusActive,
StatusInactive,
StatusPending,
}
// Actually, strum doesn't have a prefix attribute for Display
// But you can achieve similar results with naming:
#[derive(Display)]
#[strum(serialize_all = "lowercase")]
enum StatusClean {
Active, // Cleaner naming
Inactive,
Pending,
}
// Or use serialize attributes:
#[derive(Display)]
enum HttpStatus {
#[strum(serialize = "active")]
StatusActive,
#[strum(serialize = "inactive")]
StatusInactive,
#[strum(serialize = "pending")]
StatusPending,
}Use serialize attributes to strip prefixes or rename variants.
use strum::Display;
// Manual Display implementation:
enum StatusManual {
Active,
Inactive,
Pending,
}
impl std::fmt::Display for StatusManual {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StatusManual::Active => write!(f, "Active"),
StatusManual::Inactive => write!(f, "Inactive"),
StatusManual::Pending => write!(f, "Pending"),
}
}
}
// With strum::Display:
#[derive(Display)]
enum StatusStrum {
Active,
Inactive,
Pending,
}
// Both produce identical output, but strum:
// 1. Requires less boilerplate
// 2. Is automatically updated when variants change
// 3. Supports casing transformations
// 4. Less error-prone (no manual string typing)
// For more complex formatting:
#[derive(Display)]
enum ComplexStatus {
#[strum(serialize = "status:active")]
Active,
#[strum(serialize = "status:inactive")]
Inactive,
#[strum(serialize = "status:pending-review")]
PendingReview,
}The derive macro reduces boilerplate and prevents typos from manual strings.
use strum::Display;
// What strum::Display generates:
#[derive(Display)]
#[strum(serialize_all = "lowercase")]
enum Direction {
North,
South,
East,
West,
}
// Approximately generates:
impl std::fmt::Display for Direction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Direction::North => write!(f, "north"),
Direction::South => write!(f, "south"),
Direction::East => write!(f, "east"),
Direction::West => write!(f, "west"),
}
}
}
// The key points:
// 1. Match on &self
// 2. Each arm writes a static string
// 3. No runtime string allocation for simple cases
// 4. Casing is computed at compile time
fn main() {
let dir = Direction::North;
println!("{}", dir); // "north"
// The string "north" is embedded in the binary
// No heap allocation occurs
}Generated code uses static strings computed at compile time.
use strum::Display;
#[derive(Display)]
enum Simple {
A,
B,
C,
}
fn main() {
// strum::Display generates code that writes static strings
// This is as efficient as hand-written Display impl
let s = format!("{}", Simple::A);
// For even more control, use as_str():
let str_val: &'static str = Simple::A.as_str(); // Only with IntoEnumIterator
// But for pure Display, you can convert:
use std::fmt::Write;
let mut output = String::new();
write!(output, "{}", Simple::A).unwrap();
}
// If you need static string access:
#[derive(Display, strum::EnumString)]
#[strum(serialize_all = "lowercase")]
enum StaticEnum {
First,
Second,
Third,
}
impl StaticEnum {
fn as_static_str(&self) -> &'static str {
match self {
StaticEnum::First => "first",
StaticEnum::Second => "second",
StaticEnum::Third => "third",
}
}
}
// Or use strum::EnumVariantNames for this patternFor static string access, consider also using EnumVariantNames or manual as_str() methods.
use strum::{Display, EnumString, EnumIter, IntoEnumIterator};
#[derive(Display, EnumString, EnumIter, Debug)]
#[strum(serialize_all = "snake_case")]
enum Status {
Active,
Inactive,
Pending,
Complete,
}
fn main() {
// Display: format as string
println!("{}", Status::Active); // "active"
// EnumString: parse from string
use std::str::FromStr;
let status: Status = Status::from_str("active").unwrap();
println!("{:?}", status); // Active
// EnumIter: iterate over all variants
for status in Status::iter() {
println!("Variant: {}", status);
}
// active
// inactive
// pending
// complete
// Combining: format and parse roundtrip
let original = Status::Pending;
let formatted = format!("{}", original); // "pending"
let parsed: Status = formatted.parse().unwrap();
assert!(matches!(parsed, Status::Pending));
}Combine Display with EnumString for bidirectional conversion.
use strum::Display;
// For documentation or user messages:
#[derive(Display)]
#[strum(serialize_all = "title_case")]
enum UserRole {
AdminUser,
RegularUser,
GuestUser,
}
fn main() {
let role = UserRole::AdminUser;
// In user-facing messages:
println!("Welcome, {}!", role); // "Welcome, Admin User!"
// In logs:
println!("[INFO] User role: {}", role); // "[INFO] User role: Admin User"
// In error messages:
println!("Permission denied. Required role: {}", role);
}
// Compare with raw variant names:
#[derive(Debug)]
enum RoleDebug {
AdminUser,
RegularUser,
}
fn show_debug() {
let role = RoleDebug::AdminUser;
println!("{:?}", role); // "AdminUser" - not as readable
}Display provides user-friendly strings where Debug shows implementation details.
use strum::Display;
#[derive(Display)]
#[strum(serialize_all = "title_case")]
enum HttpHeader {
ContentType,
ContentLength,
Authorization,
CacheControl,
UserAgent,
Accept,
AcceptEncoding,
#[strum(serialize = "X-Custom-Header")]
XCustomHeader,
}
impl HttpHeader {
fn as_header_name(&self) -> &'static str {
// Use the Display output as header name
// This would require a separate method since Display returns String
match self {
HttpHeader::ContentType => "Content-Type",
HttpHeader::ContentLength => "Content-Length",
HttpHeader::Authorization => "Authorization",
HttpHeader::CacheControl => "Cache-Control",
HttpHeader::UserAgent => "User-Agent",
HttpHeader::Accept => "Accept",
HttpHeader::AcceptEncoding => "Accept-Encoding",
HttpHeader::XCustomHeader => "X-Custom-Header",
}
}
}
fn main() {
// Using Display for logging
println!("Processing header: {}", HttpHeader::ContentType);
// For actual header usage, you might need specific formatting
// Display gives you a starting point
}HTTP headers often need specific formatting; Display provides a base with overrides where needed.
Derive macro output:
// Input:
#[derive(Display)]
#[strum(serialize_all = "snake_case")]
enum Example {
FirstValue,
SecondValue,
#[strum(serialize = "special")]
ThirdValue,
}
// Generated (approximately):
impl std::fmt::Display for Example {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Example::FirstValue => write!(f, "first_value"),
Example::SecondValue => write!(f, "second_value"),
Example::ThirdValue => write!(f, "special"),
}
}
}Casing options:
| Option | Input | Output |
|--------|-------|--------|
| lowercase | SomeVariant | somevariant |
| UPPERCASE | SomeVariant | SOMEVARIANT |
| snake_case | SomeVariant | some_variant |
| kebab_case | SomeVariant | some-variant |
| screaming_snake_case | SomeVariant | SOME_VARIANT |
| title_case | SomeVariant | Some Variant |
| camelCase | SomeVariant | someVariant |
| PascalCase | SomeVariant | SomeVariant |
When to use strum::Display:
When to implement Display manually:
&'static strKey insight: strum::Display is a code generator that transforms enum variant names into strings according to rules specified at derive time. The macro parses variant names, applies casing transformations, and generates a match expression that writes static strings. This eliminates the boilerplate of manual Display implementations while ensuring consistency—every variant follows the same transformation rules unless explicitly overridden. The compile-time nature of the transformation means no runtime string manipulation overhead; the formatted strings are computed during macro expansion and embedded directly in the binary. Combined with other strum traits like EnumString, you get bidirectional conversion between enums and strings with a single derive attribute, making enums first-class citizens in serialization, configuration, and user interfaces.