How does strum::EnumMessage enable associating documentation strings with enum variants?

strum::EnumMessage provides a derive macro that adds get_message() and get_documentation() methods to enums, allowing you to associate human-readable messages and documentation strings with each variant through strum(message = "...") and strum(documentation = "...") attributes—enabling runtime access to variant descriptions for user interfaces, help systems, and configuration validation. Unlike standard Rust doc comments which are compile-time only, EnumMessage makes variant documentation accessible at runtime through generated methods that return Option<&'static str>, making it valuable for generating CLI help text, API documentation, and user-facing error messages.

Basic EnumMessage Usage

use strum::EnumMessage;
 
#[derive(Debug, Clone, EnumMessage)]
enum Status {
    #[strum(message = "The process is running")]
    Running,
    
    #[strum(message = "The process is stopped")]
    Stopped,
    
    #[strum(message = "The process failed with an error")]
    Failed,
}
 
fn basic_usage() {
    let status = Status::Running;
    
    // get_message() returns Option<&'static str>
    let message = status.get_message();
    println!("Status: {:?}", message);
    // Output: Some("The process is running")
    
    // Enum variants without message attribute return None
    let status = Status::Stopped;
    assert!(status.get_message().is_some());
}

EnumMessage generates a get_message() method that returns the associated string for each variant.

The message Attribute

use strum::EnumMessage;
 
#[derive(Debug, EnumMessage)]
enum HttpMethod {
    #[strum(message = "Retrieve a resource")]
    Get,
    
    #[strum(message = "Create a new resource")]
    Post,
    
    #[strum(message = "Update an existing resource")]
    Put,
    
    #[strum(message = "Remove a resource")]
    Delete,
    
    #[strum(message = "Apply partial modifications")]
    Patch,
}
 
fn display_method_help(method: HttpMethod) {
    if let Some(msg) = method.get_message() {
        println!("{}: {}", method.get_variant_name(), msg);
    }
}
 
fn main() {
    display_method_help(HttpMethod::Get);
    // Output: Get: Retrieve a resource
    
    display_method_help(HttpMethod::Post);
    // Output: Post: Create a new resource
}

The message attribute associates a human-readable description with each variant.

The documentation Attribute

use strum::EnumMessage;
 
#[derive(Debug, EnumMessage)]
enum LogLevel {
    #[strum(
        message = "Error level",
        documentation = "Critical errors that require immediate attention"
    )]
    Error,
    
    #[strum(
        message = "Warning level",
        documentation = "Non-critical issues that should be reviewed"
    )]
    Warning,
    
    #[strum(
        message = "Info level",
        documentation = "General information about application operation"
    )]
    Info,
    
    #[strum(
        message = "Debug level",
        documentation = "Detailed information for debugging purposes"
    )]
    Debug,
}
 
fn documentation_attribute() {
    let level = LogLevel::Error;
    
    // get_message() returns the short message
    println!("Message: {:?}", level.get_message());
    // Output: Some("Error level")
    
    // get_documentation() returns the longer documentation
    println!("Documentation: {:?}", level.get_documentation());
    // Output: Some("Critical errors that require immediate attention")
}

The documentation attribute provides longer, more detailed descriptions separate from the short message.

Generated Methods

use strum::EnumMessage;
 
#[derive(Debug, EnumMessage)]
enum Color {
    #[strum(message = "Red color")]
    Red,
    
    #[strum(message = "Green color")]
    Green,
    
    // No message - returns None
    Blue,
}
 
fn generated_methods() {
    let red = Color::Red;
    
    // get_message() -> Option<&'static str>
    if let Some(msg) = red.get_message() {
        println!("Red message: {}", msg);
    }
    
    // get_documentation() -> Option<&'static str>
    // Returns None unless documentation attribute is present
    let doc = red.get_documentation();
    println!("Red documentation: {:?}", doc);  // None
    
    // For variants without attributes, methods return None
    let blue = Color::Blue;
    println!("Blue message: {:?}", blue.get_message());  // None
}

The derive macro generates get_message() and get_documentation() methods that return Option<&'static str>.

Combining with Other Strum Features

use strum::{EnumMessage, EnumIter, Display, IntoEnumIterator};
 
#[derive(Debug, Clone, EnumMessage, EnumIter, Display)]
#[strum(serialize_all = "lowercase")]
enum Priority {
    #[strum(
        message = "High priority",
        documentation = "Requires immediate action within 24 hours",
        serialize = "high"
    )]
    High,
    
    #[strum(
        message = "Medium priority",
        documentation = "Should be addressed within a week",
        serialize = "medium"
    )]
    Medium,
    
    #[strum(
        message = "Low priority",
        documentation = "Can be scheduled flexibly",
        serialize = "low"
    )]
    Low,
}
 
fn combined_features() {
    // EnumIter - iterate over all variants
    for priority in Priority::iter() {
        // Display - convert to string
        let serialized = priority.to_string();
        
        // EnumMessage - get descriptions
        let message = priority.get_message().unwrap_or("Unknown");
        let doc = priority.get_documentation().unwrap_or("");
        
        println!("{}: {} ({})", serialized, message, doc);
    }
    
    // Output:
    // high: High priority (Requires immediate action within 24 hours)
    // medium: Medium priority (Should be addressed within a week)
    // low: Low priority (Can be scheduled flexibly)
}

EnumMessage integrates with other strum macros like EnumIter and Display for comprehensive enum handling.

CLI Help Text Generation

use strum::EnumMessage;
 
#[derive(Debug, Clone, EnumMessage)]
enum Command {
    #[strum(
        message = "List all items",
        documentation = "Display a list of all items in the database with optional filtering"
    )]
    List,
    
    #[strum(
        message = "Create a new item",
        documentation = "Create a new item in the database with the specified attributes"
    )]
    Create,
    
    #[strum(
        message = "Update an existing item",
        documentation = "Modify an existing item's attributes by its identifier"
    )]
    Update,
    
    #[strum(
        message = "Delete an item",
        documentation = "Permanently remove an item from the database"
    )]
    Delete,
    
    #[strum(
        message = "Show help",
        documentation = "Display detailed help information for commands"
    )]
    Help,
}
 
fn print_help() {
    println!("Available commands:");
    println!();
    
    for cmd in &[Command::List, Command::Create, Command::Update, Command::Delete, Command::Help] {
        let msg = cmd.get_message().unwrap_or("No description");
        let doc = cmd.get_documentation().unwrap_or("");
        
        let variant_name = format!("{:?}", cmd).to_lowercase();
        println!("  {:10} {}", variant_name, msg);
        println!("             {}", doc);
        println!();
    }
}
 
fn main() {
    print_help();
    // Output:
    // Available commands:
    //
    //   list       List all items
    //              Display a list of all items in the database with optional filtering
    //
    //   create     Create a new item
    //              Create a new item in the database with the specified attributes
    // ...
}

EnumMessage is ideal for generating CLI help text from enum variants.

API Documentation Generation

use strum::EnumMessage;
use serde::Serialize;
 
#[derive(Debug, Clone, Serialize, EnumMessage)]
#[serde(rename_all = "lowercase")]
enum ApiEndpoint {
    #[strum(
        message = "User management",
        documentation = "Endpoints for creating, updating, and deleting users"
    )]
    Users,
    
    #[strum(
        message = "Authentication",
        documentation = "OAuth2 and token-based authentication endpoints"
    )]
    Auth,
    
    #[strum(
        message = "Data queries",
        documentation = "Read-only endpoints for querying data with pagination support"
    )]
    Query,
}
 
#[derive(Serialize)]
struct EndpointInfo {
    name: String,
    short_description: Option<&'static str>,
    long_description: Option<&'static str>,
}
 
fn get_endpoint_docs() -> Vec<EndpointInfo> {
    use strum::IntoEnumIterator;
    
    // Would need EnumIter derive for IntoEnumIterator
    let endpoints = vec![ApiEndpoint::Users, ApiEndpoint::Auth, ApiEndpoint::Query];
    
    endpoints
        .into_iter()
        .map(|endpoint| EndpointInfo {
            name: format!("{:?}", endpoint).to_lowercase(),
            short_description: endpoint.get_message(),
            long_description: endpoint.get_documentation(),
        })
        .collect()
}

Use EnumMessage to generate API documentation from enum definitions.

Configuration Validation Messages

use strum::EnumMessage;
 
#[derive(Debug, Clone, EnumMessage)]
enum ConfigError {
    #[strum(
        message = "Invalid configuration file",
        documentation = "The configuration file contains syntax errors or invalid format"
    )]
    InvalidFile,
    
    #[strum(
        message = "Missing required field",
        documentation = "A required configuration field was not provided"
    )]
    MissingField,
    
    #[strum(
        message = "Invalid value",
        documentation = "A configuration value is outside the allowed range or format"
    )]
    InvalidValue,
    
    #[strum(
        message = "Permission denied",
        documentation = "The application lacks permission to read the configuration"
    )]
    PermissionDenied,
}
 
fn handle_config_error(error: ConfigError) -> String {
    let message = error.get_message().unwrap_or("Unknown error");
    let documentation = error.get_documentation().unwrap_or("No additional information");
    
    format!("{}: {}", message, documentation)
}
 
fn validation_example() {
    let error = ConfigError::MissingField;
    let user_message = handle_config_error(error);
    
    println!("{}", user_message);
    // Output: Missing required field: A required configuration field was not provided
}

Associate user-friendly error messages with configuration validation errors.

EnumMessage vs Doc Comments

use strum::EnumMessage;
 
/// Standard doc comments are compile-time only
/// They cannot be accessed at runtime
#[derive(Debug, EnumMessage)]
enum Transport {
    /// Use HTTP protocol (not accessible at runtime)
    #[strum(message = "HTTP transport")]
    Http,
    
    /// Use HTTPS protocol with TLS
    #[strum(
        message = "HTTPS transport",
        documentation = "Secure HTTP with TLS encryption for sensitive data"
    )]
    Https,
    
    /// Use WebSocket for real-time communication
    #[strum(message = "WebSocket transport")]
    WebSocket,
}
 
fn doc_comments_vs_enum_message() {
    // Doc comments are NOT accessible at runtime
    // You cannot extract "HTTP transport" from doc comments
    
    // EnumMessage IS accessible at runtime
    let transport = Transport::Https;
    let message = transport.get_message();  // Some("HTTPS transport")
    let doc = transport.get_documentation(); // Some("Secure HTTP with TLS encryption...")
    
    println!("Message: {:?}", message);
    println!("Documentation: {:?}", doc);
}

Doc comments are compile-time only; EnumMessage provides runtime-accessible descriptions.

Selective Message Association

use strum::EnumMessage;
 
#[derive(Debug, EnumMessage)]
enum Feature {
    // Has both message and documentation
    #[strum(
        message = "User authentication",
        documentation = "Login, logout, and session management"
    )]
    Auth,
    
    // Has only message
    #[strum(message = "User profiles")]
    Profiles,
    
    // Has only documentation
    #[strum(documentation = "Internal feature not exposed to users")]
    Internal,
    
    // Has neither
    Experimental,
}
 
fn selective_messages() {
    // Both message and documentation
    let auth = Feature::Auth;
    assert!(auth.get_message().is_some());
    assert!(auth.get_documentation().is_some());
    
    // Only message
    let profiles = Feature::Profiles;
    assert!(profiles.get_message().is_some());
    assert!(profiles.get_documentation().is_none());
    
    // Only documentation
    let internal = Feature::Internal;
    assert!(internal.get_message().is_none());
    assert!(internal.get_documentation().is_some());
    
    // Neither
    let experimental = Feature::Experimental;
    assert!(experimental.get_message().is_none());
    assert!(experimental.get_documentation().is_none());
}

You can selectively add message, documentation, or both to each variant.

Localized Messages Pattern

use strum::EnumMessage;
use std::collections::HashMap;
 
#[derive(Debug, Clone, EnumMessage, Hash, Eq, PartialEq)]
enum MessageKey {
    #[strum(message = "welcome")]
    Welcome,
    
    #[strum(message = "goodbye")]
    Goodbye,
    
    #[strum(message = "error_not_found")]
    NotFound,
    
    #[strum(message = "error_permission")]
    PermissionDenied,
}
 
struct Localizer {
    translations: HashMap<String, HashMap<String, String>>,
}
 
impl Localizer {
    fn new() -> Self {
        let mut en = HashMap::new();
        en.insert("welcome".to_string(), "Welcome!".to_string());
        en.insert("goodbye".to_string(), "Goodbye!".to_string());
        en.insert("error_not_found".to_string(), "Resource not found".to_string());
        en.insert("error_permission".to_string(), "Permission denied".to_string());
        
        let mut es = HashMap::new();
        es.insert("welcome".to_string(), "”Bienvenido!".to_string());
        es.insert("goodbye".to_string(), "”Adiós!".to_string());
        es.insert("error_not_found".to_string(), "Recurso no encontrado".to_string());
        es.insert("error_permission".to_string(), "Permiso denegado".to_string());
        
        let mut translations = HashMap::new();
        translations.insert("en".to_string(), en);
        translations.insert("es".to_string(), es);
        
        Localizer { translations }
    }
    
    fn get(&self, key: &MessageKey, lang: &str) -> &str {
        let msg_key = key.get_message().unwrap();  // Use message as key
        self.translations
            .get(lang)
            .and_then(|lang_map| lang_map.get(msg_key))
            .map(|s| s.as_str())
            .unwrap_or(msg_key)
    }
}
 
fn localization_example() {
    let localizer = Localizer::new();
    
    let welcome = MessageKey::Welcome;
    
    println!("EN: {}", localizer.get(&welcome, "en"));  // "Welcome!"
    println!("ES: {}", localizer.get(&welcome, "es"));  // "”Bienvenido!"
}

Use EnumMessage values as keys for localization systems.

EnumMessage for Validation Rules

use strum::EnumMessage;
 
#[derive(Debug, Clone, EnumMessage)]
enum ValidationError {
    #[strum(
        message = "Value too short",
        documentation = "Minimum length requirement not met"
    )]
    TooShort,
    
    #[strum(
        message = "Value too long",
        documentation = "Maximum length exceeded"
    )]
    TooLong,
    
    #[strum(
        message = "Invalid format",
        documentation = "Value does not match expected pattern"
    )]
    InvalidFormat,
    
    #[strum(
        message = "Value out of range",
        documentation = "Numeric value outside allowed bounds"
    )]
    OutOfRange,
}
 
struct ValidationResult {
    is_valid: bool,
    errors: Vec<(String, ValidationError)>,
}
 
impl ValidationResult {
    fn summarize(&self) -> String {
        if self.is_valid {
            return "Validation passed".to_string();
        }
        
        let messages: Vec<String> = self.errors
            .iter()
            .map(|(field, error)| {
                let msg = error.get_message().unwrap_or("Validation error");
                format!("{}: {}", field, msg)
            })
            .collect();
        
        messages.join("\n")
    }
}
 
fn validation_example() {
    let result = ValidationResult {
        is_valid: false,
        errors: vec![
            ("username".to_string(), ValidationError::TooShort),
            ("email".to_string(), ValidationError::InvalidFormat),
        ],
    };
    
    println!("{}", result.summarize());
    // Output:
    // username: Value too short
    // email: Invalid format
}

Associate validation error messages with enum variants for consistent user feedback.

Runtime Enum Inspection

use strum::EnumMessage;
use strum::IntoEnumIterator;
 
#[derive(Debug, Clone, EnumMessage)]
enum Permission {
    #[strum(message = "Read access", documentation = "View and download resources")]
    Read,
    
    #[strum(message = "Write access", documentation = "Create and modify resources")]
    Write,
    
    #[strum(message = "Admin access", documentation = "Full control including user management")]
    Admin,
}
 
// Need to add EnumIter derive for IntoEnumIterator
use strum::EnumIter;
 
#[derive(Debug, Clone, EnumMessage, EnumIter)]
enum Role {
    #[strum(message = "Viewer", documentation = "Read-only access")]
    Viewer,
    
    #[strum(message = "Editor", documentation = "Read and write access")]
    Editor,
    
    #[strum(message = "Administrator", documentation = "Full administrative access")]
    Administrator,
}
 
fn inspect_roles() {
    println!("Available roles:");
    for role in Role::iter() {
        let name = format!("{:?}", role);
        let msg = role.get_message().unwrap_or("Unknown");
        let doc = role.get_documentation().unwrap_or("");
        
        println!("  {} - {} ({})", name, msg, doc);
    }
}

Combine EnumMessage with EnumIter to inspect all variants and their descriptions at runtime.

Synthesis

How EnumMessage works:

// The derive macro generates:
impl EnumMessage for Status {
    fn get_message(&self) -> Option<&'static str> {
        match self {
            Status::Running => Some("The process is running"),
            Status::Stopped => Some("The process is stopped"),
            Status::Failed => Some("The process failed"),
        }
    }
    
    fn get_documentation(&self) -> Option<&'static str> {
        match self {
            Status::Running => Some("Detailed description..."),
            // ...
        }
    }
}

Use cases:

// 1. CLI help text generation
// 2. API documentation
// 3. Error messages for users
// 4. Localization key management
// 5. Configuration validation messages
// 6. Runtime enum introspection
// 7. User-facing enum descriptions

Key distinction from doc comments:

/// Doc comments: compile-time only, for rustdoc
//  Cannot be accessed at runtime
//  Used for library documentation
 
#[strum(message = "...")]
// EnumMessage: runtime accessible
//  Can be retrieved via get_message()
//  Used for user-facing messages

Message vs documentation attributes:

#[strum(message = "Short description")]  // Brief, for UI labels
#[strum(documentation = "Long description")]  // Detailed, for help text
 
// message: Short, user-facing descriptions
// documentation: Longer, explanatory text
 
// Both return Option<&'static str>
// Can be used independently or together

Key insight: strum::EnumMessage bridges the gap between compile-time documentation (doc comments) and runtime-accessible descriptions by generating get_message() and get_documentation() methods that return Option<&'static str> for each enum variant. The message attribute provides short descriptions ideal for labels and UI text, while documentation provides longer explanations suitable for help text and detailed documentation. Unlike standard Rust doc comments which are stripped during compilation, EnumMessage makes variant descriptions available at runtime—enabling CLI help generation, API documentation systems, localized message keys, and user-facing error messages. The derive macro integrates seamlessly with other strum features like EnumIter and Display, allowing you to iterate over variants and their descriptions dynamically, making it a powerful tool for building introspectable, self-documenting enums.