Loading page…
Rust walkthroughs
Loading page…
strum::Display derive and how does it differ from implementing Display manually?strum::Display is a derive macro that automatically implements the std::fmt::Display trait for enums, generating string representations from variant names. Unlike implementing Display manually with custom formatting logic, strum::Display uses the variant name as the display output by default, with optional customization through attributes. This reduces boilerplate for simple cases while providing escape hatches for custom formatting when needed.
use strum::Display;
#[derive(Display)]
enum Status {
Active,
Inactive,
Pending,
}
fn main() {
let status = Status::Active;
println!("Status: {}", status); // Prints: Status: Active
let status = Status::Pending;
println!("{}", status); // Prints: Pending
}The derive macro generates a Display implementation that outputs the variant name.
use std::fmt;
enum Status {
Active,
Inactive,
Pending,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::Active => write!(f, "Active"),
Status::Inactive => write!(f, "Inactive"),
Status::Pending => write!(f, "Pending"),
}
}
}
fn main() {
let status = Status::Active;
println!("Status: {}", status); // Prints: Status: Active
}Manual implementation requires writing the match statement yourself.
use strum::Display;
#[derive(Display)]
enum Status {
#[strum(to_string = "Currently Active")]
Active,
#[strum(to_string = "Not Active")]
Inactive,
Pending, // Uses default: "Pending"
}
fn main() {
println!("{}", Status::Active); // Currently Active
println!("{}", Status::Inactive); // Not Active
println!("{}", Status::Pending); // Pending
}Use #[strum(to_string = "...")] to customize the display string.
use std::fmt;
enum Status {
Active,
Inactive,
Pending,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::Active => write!(f, "Currently Active"),
Status::Inactive => write!(f, "Not Active"),
Status::Pending => write!(f, "Pending"),
}
}
}Manual implementation requires writing each case explicitly.
use strum::Display;
#[derive(Display)]
#[strum(serialize_all = "snake_case")]
enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
}
fn main() {
println!("{}", HttpMethod::Get); // get
println!("{}", HttpMethod::Post); // post
println!("{}", HttpMethod::Delete); // delete
}
#[derive(Display)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
enum ErrorCode {
NotFound,
InternalError,
BadRequest,
}
fn main() {
println!("{}", ErrorCode::NotFound); // NOT_FOUND
println!("{}", ErrorCode::InternalError); // INTERNAL_ERROR
}
#[derive(Display)]
#[strum(serialize_all = "kebab-case")]
enum Color {
LightBlue,
DarkRed,
BrightGreen,
}
fn main() {
println!("{}", Color::LightBlue); // light-blue
println!("{}", Color::DarkRed); // dark-red
}strum supports various case transformations out of the box.
use std::fmt;
enum Temperature {
Celsius(f64),
Fahrenheit(f64),
Kelvin(f64),
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Temperature::Celsius(v) => write!(f, "{:.1}°C", v),
Temperature::Fahrenheit(v) => write!(f, "{:.1}°F", v),
Temperature::Kelvin(v) => write!(f, "{:.1}K", v),
}
}
}
fn main() {
let temp = Temperature::Celsius(22.5);
println!("{}", temp); // 22.5°C
let temp = Temperature::Fahrenheit(72.3);
println!("{}", temp); // 72.3°F
}Manual implementation handles variants with data and dynamic formatting.
use strum::Display;
#[derive(Display)]
enum Message {
#[strum(to_string = "Hello, {0}!")]
Greeting(String),
#[strum(to_string = "Error code: {0}")]
ErrorCode(u32),
#[strum(to_string = "User {name} is {age} years old")]
UserInfo { name: String, age: u32 },
Simple,
}
fn main() {
println!("{}", Message::Greeting("Alice".to_string())); // Hello, Alice!
println!("{}", Message::ErrorCode(404)); // Error code: 404
println!("{}", Message::UserInfo {
name: "Bob".to_string(),
age: 30
}); // User Bob is 30 years old
println!("{}", Message::Simple); // Simple
}strum supports format strings with field placeholders in to_string.
use std::fmt;
enum HttpStatus {
Ok,
NotFound,
ServerError { code: u16, message: String },
Redirect(String),
}
impl fmt::Display for HttpStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HttpStatus::Ok => write!(f, "200 OK"),
HttpStatus::NotFound => write!(f, "404 Not Found"),
HttpStatus::ServerError { code, message } => {
write!(f, "{} {}", code, message)
}
HttpStatus::Redirect(url) => {
write!(f, "Redirecting to {}", url)
}
}
}
}
fn main() {
println!("{}", HttpStatus::Ok); // 200 OK
println!("{}", HttpStatus::ServerError {
code: 500,
message: "Internal Error".to_string()
}); // 500 Internal Error
}Manual implementation provides full control over complex formatting logic.
// With strum::Display - minimal boilerplate
use strum::Display;
#[derive(Display)]
enum Direction {
North,
South,
East,
West,
}
// Without strum - repetitive match arms
use std::fmt;
enum DirectionManual {
North,
South,
East,
West,
}
impl fmt::Display for DirectionManual {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DirectionManual::North => write!(f, "North"),
DirectionManual::South => write!(f, "South"),
DirectionManual::East => write!(f, "East"),
DirectionManual::West => write!(f, "West"),
}
}
}For simple cases, strum::Display eliminates repetitive match arms.
use strum::Display;
#[derive(Display)]
enum Priority {
#[strum(to_string = "🔴 High Priority")]
High,
#[strum(to_string = "🟡 Medium Priority")]
Medium,
#[strum(to_string = "🟢 Low Priority")]
Low,
}
#[derive(Display)]
#[strum(serialize_all = "title_case")]
enum Role {
SystemAdmin,
RegularUser,
GuestUser,
}
fn main() {
println!("{}", Priority::High); // 🔴 High Priority
println!("{}", Role::SystemAdmin); // System Admin
println!("{}", Role::RegularUser); // Regular User
}Combine to_string with emojis, prefixes, and case transformations.
use std::fmt;
enum Level {
Debug,
Info,
Warning,
Error,
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Can add conditional logic
if f.alternate() {
match self {
Level::Debug => write!(f, "DBG"),
Level::Info => write!(f, "INF"),
Level::Warning => write!(f, "WRN"),
Level::Error => write!(f, "ERR"),
}
} else {
match self {
Level::Debug => write!(f, "DEBUG"),
Level::Info => write!(f, "INFO"),
Level::Warning => write!(f, "WARNING"),
Level::Error => write!(f, "ERROR"),
}
}
}
}
fn main() {
println!("{}", Level::Debug); // DEBUG
println!("{:#}", Level::Debug); // DBG
}Manual implementation can use formatter flags and conditional logic.
use strum::{Display, EnumString, IntoStaticStr, VariantNames};
#[derive(Display, EnumString, IntoStaticStr, VariantNames)]
enum Color {
Red,
Green,
Blue,
}
fn main() {
// Display
println!("{}", Color::Red); // Red
// FromStr (EnumString)
let color: Color = "Green".parse().unwrap();
// Into &str (IntoStaticStr)
let s: &str = Color::Blue.into();
println!("{}", s); // Blue
// Variant names (VariantNames)
println!("{:?}", Color::VARIANTS); // ["Red", "Green", "Blue"]
}strum::Display integrates with other strum derives for consistent behavior.
use strum::Display;
// strum cannot do this:
// - Access external state in formatting
// - Implement conditional formatting based on formatter flags
// - Use complex logic to determine output
// - Format nested data structures
// For these cases, use manual implementation
use std::fmt;
enum LogLevel {
Debug,
Info,
Warning,
Error,
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (icon, name) = match self {
LogLevel::Debug => ("🐛", "DEBUG"),
LogLevel::Info => ("ℹ️", "INFO"),
LogLevel::Warning => ("⚠️", "WARN"),
LogLevel::Error => ("❌", "ERROR"),
};
// Complex formatting with width and alignment
write!(f, "{} {:5}", icon, name)
}
}Manual implementation enables complex formatting that strum cannot express.
use strum::Display;
use std::fmt;
// strum::Display - generated code is hidden
// Pros: Less code to maintain
// Cons: Need to understand macro behavior
#[derive(Display)]
enum Simple {
A,
B,
C,
}
// Manual implementation - explicit and documented
// Pros: Clear behavior, IDE support
// Cons: More boilerplate
enum SimpleManual {
A,
B,
C,
}
impl fmt::Display for SimpleManual {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Clear what each variant outputs
match self {
SimpleManual::A => write!(f, "A"),
SimpleManual::B => write!(f, "B"),
SimpleManual::C => write!(f, "C"),
}
}
}Consider maintenance and readability when choosing between approaches.
use strum::Display;
use std::fmt;
// Both approaches have similar runtime performance
// The derive macro generates similar code to manual implementation
#[derive(Display)]
enum Derived {
Variant1,
Variant2,
}
// strum generates essentially:
impl fmt::Display for Derived {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Derived::Variant1 => write!(f, "Variant1"),
Derived::Variant2 => write!(f, "Variant2"),
}
}
}
// No runtime overhead from using the derive macroThe derive macro generates equivalent code to manual implementation.
use strum::Display;
use std::fmt;
// Use strum::Display when:
// - Simple variant-to-string mapping
// - Consistent naming patterns
// - Multiple strum derives needed
// - Reducing boilerplate
#[derive(Display)]
#[strum(serialize_all = "snake_case")]
enum HttpHeader {
ContentType,
ContentLength,
CacheControl,
}
// Use manual implementation when:
// - Complex formatting logic
// - Conditional output
// - Accessing variant data in formatting
// - Using formatter flags (width, precision, alternate)
enum Temperature {
Celsius(f64),
Fahrenheit(f64),
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Temperature::Celsius(v) => {
if let Some(precision) = f.precision() {
write!(f, "{:.prec$}°C", v, prec = precision)
} else {
write!(f, "{:.1}°C", v)
}
}
Temperature::Fahrenheit(v) => {
write!(f, "{:.1}°F", v)
}
}
}
}
fn main() {
let temp = Temperature::Celsius(22.567);
println!("{}", temp); // 22.6°C
println!("{:.2}", temp); // 22.57°C
}Choose based on complexity and formatting needs.
| Feature | strum::Display | Manual Display |
|---------|------------------|------------------|
| Boilerplate | Minimal | More verbose |
| Custom strings | to_string attribute | write! macro |
| Case transforms | Built-in | Manual |
| Format strings | Basic {0} placeholders | Full format spec |
| Formatter flags | No | Yes |
| Conditional logic | No | Yes |
| External state | No | Yes |
| Compile-time checks | Generated code | Explicit code |
strum::Display and manual Display implementation serve different needs:
Use strum::Display when:
Use manual implementation when:
Key insight: strum::Display is a convenience tool for the common case of mapping enum variants to static strings. It handles the 80% case elegantly while manual implementation remains available for complex formatting needs. The derive macro generates code equivalent to what you'd write manually, so there's no runtime cost—only development convenience or explicitness depending on your preference.