What is the difference between strum::AsRefStr and strum::IntoStaticStr for string conversion from enums?

strum::AsRefStr and strum::IntoStaticStr are derive macros in the strum crate that provide different ownership semantics for converting enum variants to string references. AsRefStr implements AsRef<str>, returning a borrowed reference to the variant's string representation with lifetime tied to the enum instance—it works with any display representation. IntoStaticStr implements Into<&'static str>, returning a 'static lifetime string reference that can outlive the enum instance, but only works when the string representation is statically known at compile time (such as using the variant name directly). The key trade-off is flexibility versus ownership: AsRefStr supports custom #[strum(to_string = "...")] attributes but requires the enum to remain in scope, while IntoStaticStr provides 'static references but only for fixed variant names.

AsRefStr: Borrowed References

use strum::AsRefStr;
 
#[derive(AsRefStr)]
enum Status {
    Active,
    Inactive,
    Pending,
}
 
fn demonstrate_as_ref() {
    let status = Status::Active;
    
    // Returns &str borrowed from the enum instance
    let s: &str = status.as_ref();
    
    // s is valid only while status is in scope
    println!("Status: {}", s);
    
    // Works with standard AsRef pattern
    fn accepts_as_ref(value: impl AsRef<str>) {
        println!("Got: {}", value.as_ref());
    }
    
    accepts_as_ref(&status);
    accepts_as_ref(status);
}

AsRefStr returns a borrowed reference tied to the enum's lifetime.

IntoStaticStr: Static References

use strum::IntoStaticStr;
 
#[derive(IntoStaticStr)]
enum Status {
    Active,
    Inactive,
    Pending,
}
 
fn demonstrate_into_static() {
    let status = Status::Active;
    
    // Returns &'static str that lives forever
    let s: &'static str = status.into();
    
    // Can use s even after status is dropped
    println!("Status: {}", s);
    
    // Useful for returning strings from functions
    fn get_status_name(status: Status) -> &'static str {
        status.into()
    }
    
    let static_str: &'static str = get_status_name(Status::Pending);
    // static_str lives forever, no lifetime constraints
}

IntoStaticStr returns 'static references independent of the enum instance.

Custom String Representations

use strum::{AsRefStr, IntoStaticStr};
 
// AsRefStr supports custom to_string values
#[derive(AsRefStr)]
enum StatusWithCustom {
    #[strum(to_string = "status-active")]
    Active,
    #[strum(to_string = "status-inactive")]
    Inactive,
}
 
fn custom_as_ref() {
    let status = StatusWithCustom::Active;
    assert_eq!(status.as_ref(), "status-active");
}
 
// IntoStaticStr ignores custom to_string - uses variant name
#[derive(IntoStaticStr)]
enum StatusWithCustomIgnored {
    #[strum(to_string = "status-active")]  // Ignored by IntoStaticStr
    Active,
    #[strum(to_string = "status-inactive")]  // Ignored by IntoStaticStr
    Inactive,
}
 
fn custom_into_static() {
    let status = StatusWithCustomIgnored::Active;
    // Uses variant name, not custom string
    assert_eq!(status.into(), "Active");
}

AsRefStr respects to_string attributes; IntoStaticStr uses variant names directly.

Lifetime Implications

use strum::{AsRefStr, IntoStaticStr};
 
#[derive(AsRefStr)]
enum Color {
    Red,
    Green,
    Blue,
}
 
#[derive(IntoStaticStr)]
enum Shape {
    Circle,
    Square,
    Triangle,
}
 
fn lifetime_comparison() {
    // AsRefStr: borrowed reference
    let color = Color::Red;
    let color_ref: &str = color.as_ref();
    // color_ref lifetime tied to color
    
    // IntoStaticStr: static reference
    let shape = Shape::Circle;
    let shape_static: &'static str = shape.into();
    // shape_static has 'static lifetime
}
 
// IntoStaticStr enables returning static strings
fn get_shape_name(shape: Shape) -> &'static str {
    shape.into()  // Works because IntoStaticStr returns 'static
}
 
// AsRefStr requires the enum to live long enough
fn get_color_name(color: &Color) -> &str {
    color.as_ref()  // Lifetime tied to color reference
}
 
// This would NOT compile with AsRefStr:
// fn get_color_name_bad(color: Color) -> &'static str {
//     color.as_ref()  // ERROR: cannot return reference to dropped value
// }
 
// This compiles with IntoStaticStr:
fn get_shape_name_owned(shape: Shape) -> &'static str {
    shape.into()  // OK: returns 'static str
}

IntoStaticStr enables returning &'static str from functions; AsRefStr cannot.

Display Trait Comparison

use strum::{AsRefStr, IntoStaticStr, Display};
 
// All three together
#[derive(AsRefStr, IntoStaticStr, Display)]
enum Direction {
    North,
    South,
    East,
    West,
}
 
fn comparing_traits() {
    let dir = Direction::North;
    
    // AsRefStr: AsRef<str> implementation
    let as_ref: &str = dir.as_ref();
    assert_eq!(as_ref, "North");
    
    // IntoStaticStr: Into<&'static str> implementation
    let into_static: &'static str = dir.into();
    assert_eq!(into_static, "North");
    
    // Display: std::fmt::Display implementation
    let display = format!("{}", dir);
    assert_eq!(display, "North");
    
    // Key differences:
    // - as_ref() requires dir to remain in scope
    // - into() consumes dir, returns 'static str
    // - format!() allocates a new String
}

Three different trait implementations with different ownership semantics.

Use with Collections

use strum::{AsRefStr, IntoStaticStr};
use std::collections::HashMap;
 
#[derive(AsRefStr, IntoStaticStr, Clone, Copy)]
enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
}
 
fn collections_comparison() {
    // AsRefStr: Works with references
    let methods = [HttpMethod::Get, HttpMethod::Post];
    let names: Vec<&str> = methods.iter().map(|m| m.as_ref()).collect();
    // names borrows from methods
    
    // IntoStaticStr: Owns the strings (static lifetime)
    let methods2 = [HttpMethod::Get, HttpMethod::Post, HttpMethod::Put];
    let names2: Vec<&'static str> = methods2.iter().map(|m| (*m).into()).collect();
    // names2 has 'static lifetime, methods2 can be dropped
    
    // Useful for map keys
    let mut routes: HashMap<&'static str, fn()> = HashMap::new();
    routes.insert(HttpMethod::Get.into(), handle_get);
    routes.insert(HttpMethod::Post.into(), handle_post);
}
 
fn handle_get() {}
fn handle_post() {}

IntoStaticStr is useful when you need collection elements to outlive the source.

Pattern Matching and Static Strings

use strum::IntoStaticStr;
 
#[derive(IntoStaticStr)]
enum Message {
    Start,
    Stop,
    Pause,
    Resume,
}
 
fn match_static_string(msg: Message) {
    // Convert to static string for matching
    let s: &'static str = msg.into();
    
    match s {
        "Start" => println!("Starting..."),
        "Stop" => println!("Stopping..."),
        "Pause" => println!("Pausing..."),
        "Resume" => println!("Resuming..."),
        _ => unreachable!(),
    }
}
 
// Compare with enum matching (preferred approach)
fn match_enum(msg: Message) {
    match msg {
        Message::Start => println!("Starting..."),
        Message::Stop => println!("Stopping..."),
        Message::Pause => println!("Pausing..."),
        Message::Resume => println!("Resuming..."),
    }
}

Static strings from IntoStaticStr enable string-based pattern matching.

Serialization Use Cases

use strum::{AsRefStr, IntoStaticStr};
use serde::Serialize;
 
// AsRefStr for serialization with borrowed strings
#[derive(AsRefStr, Serialize)]
#[serde(into = "&str")]
enum StatusAsRef {
    Active,
    Inactive,
}
 
// IntoStaticStr for serialization with static strings
#[derive(IntoStaticStr, Serialize)]
#[serde(into = "&'static str")]
enum StatusStatic {
    Active,
    Inactive,
}
 
fn serialization() {
    // Both work for serialization
    let status = StatusAsRef::Active;
    let json = serde_json::to_string(&status).unwrap();
    assert_eq!(json, r#""Active""#);
}

IntoStaticStr can eliminate lifetime complexity in serialization scenarios.

Conversion to String

use strum::{AsRefStr, IntoStaticStr};
 
#[derive(AsRefStr, IntoStaticStr)]
enum Size {
    Small,
    Medium,
    Large,
}
 
fn to_string_comparison() {
    let size = Size::Medium;
    
    // AsRefStr: AsRef -> &str, then to_string allocates
    let s1: String = size.as_ref().to_string();
    
    // IntoStaticStr: into() gives &'static str, then to_string allocates
    let s2: String = size.into();
    let s2: String = s2.to_string();
    
    // Both result in owned String, but different paths:
    // AsRefStr: enum -> &str -> String (borrowed intermediate)
    // IntoStaticStr: enum -> &'static str -> String (static intermediate)
}

Both can produce owned String, but the intermediate references differ.

Generic Bounds

use strum::{AsRefStr, IntoStaticStr};
 
#[derive(AsRefStr, IntoStaticStr)]
enum Priority {
    Low,
    Medium,
    High,
}
 
// Function accepting AsRefStr types
fn process_as_ref<T: AsRef<str>>(value: T) {
    println!("Processing: {}", value.as_ref());
}
 
// Function accepting IntoStaticStr types
fn process_static<T: Into<&'static str>>(value: T) {
    let s: &'static str = value.into();
    println!("Processing: {}", s);
}
 
fn using_generic_bounds() {
    let priority = Priority::High;
    
    // AsRefStr works with AsRef bounds
    process_as_ref(&priority);
    
    // IntoStaticStr works with Into<&'static str> bounds
    process_static(priority);
}

Different trait bounds for different use cases.

Performance Characteristics

use strum::{AsRefStr, IntoStaticStr};
 
#[derive(AsRefStr, IntoStaticStr, Clone, Copy)]
enum Value {
    A,
    B,
    C,
}
 
fn performance() {
    // AsRefStr: Reference to variant's string representation
    // - No allocation
    // - Reference tied to enum lifetime
    // - Slight overhead for dynamic dispatch through trait
    
    // IntoStaticStr: Static string literal
    // - No allocation
    // - 'static lifetime
    // - Direct pointer to static data
    
    // Both are zero-cost at runtime for the conversion itself
    // The difference is lifetime management
}

Both conversions are zero-allocation; the difference is lifetime semantics.

Combined Usage

use strum::{AsRefStr, IntoStaticStr, Display, EnumString};
 
// Derive multiple string traits together
#[derive(AsRefStr, IntoStaticStr, Display, EnumString, Clone, Copy)]
enum Protocol {
    #[strum(to_string = "http", serialize = "http")]
    Http,
    #[strum(to_string = "https", serialize = "https")]
    Https,
    #[strum(to_string = "ws", serialize = "ws")]
    WebSocket,
    #[strum(to_string = "wss", serialize = "wss")]
    WebSocketSecure,
}
 
fn combined_usage() {
    let proto = Protocol::Https;
    
    // Different use cases:
    
    // AsRefStr - for borrowing when enum lives long enough
    let borrowed: &str = proto.as_ref();
    assert_eq!(borrowed, "https");
    
    // IntoStaticStr - for 'static references
    let static_ref: &'static str = proto.into();
    // Note: IntoStaticStr ignores to_string, uses variant name
    assert_eq!(static_ref, "Https");  // Variant name!
    
    // Display - for user-facing output
    let display: String = format!("{}", proto);
    assert_eq!(display, "https");  // Uses to_string
    
    // EnumString - for parsing strings back to enum
    let parsed: Protocol = "https".parse().unwrap();
    assert_eq!(parsed, Protocol::Https);
}

Combining traits provides flexibility for different scenarios.

Real-World Example: HTTP Headers

use strum::{AsRefStr, IntoStaticStr};
 
#[derive(AsRefStr, IntoStaticStr)]
enum HeaderName {
    ContentType,
    ContentLength,
    Authorization,
    CacheControl,
}
 
impl HeaderName {
    // Using IntoStaticStr for 'static header names
    fn as_static_str(self) -> &'static str {
        self.into()
    }
}
 
fn http_headers() {
    let header = HeaderName::ContentType;
    
    // Header name as 'static str - useful for header maps
    let name: &'static str = header.into();
    
    // Can use in static contexts
    static DEFAULT_HEADERS: &[&'static str] = &[
        "ContentType",  // Can't use HeaderName::ContentType.into() here
        "ContentLength",
    ];
    
    // At runtime, convert for use
    let headers: Vec<&'static str> = vec![
        HeaderName::ContentType.into(),
        HeaderName::Authorization.into(),
    ];
}

IntoStaticStr provides 'static references useful for static contexts.

Real-World Example: Log Levels

use strum::{AsRefStr, IntoStaticStr};
 
#[derive(AsRefStr, IntoStaticStr, Clone, Copy)]
enum LogLevel {
    Trace,
    Debug,
    Info,
    Warn,
    Error,
}
 
fn log_level_example() {
    let level = LogLevel::Error;
    
    // AsRefStr: Good for temporary references
    fn log_message(level: &LogLevel, msg: &str) {
        println!("[{}] {}", level.as_ref(), msg);
    }
    log_message(&level, "Something went wrong");
    
    // IntoStaticStr: Good for static storage
    static LEVEL_NAMES: &[&'static str] = &["Trace", "Debug", "Info", "Warn", "Error"];
    
    // Convert to static for lookups
    let level_name: &'static str = level.into();
    assert!(LEVEL_NAMES.contains(&level_name));
}

Different traits for different lifetime requirements.

Synthesis

Ownership and Lifetime Comparison:

Aspect AsRefStr IntoStaticStr
Returns &str (borrowed) &'static str
Lifetime Tied to enum instance 'static
Custom strings Supported via to_string Ignored (variant name only)
Method .as_ref() .into()
Trait AsRef<str> Into<&'static str>
Consumes No Yes

When to use each:

Scenario Choice Reason
Temporary reference AsRefStr Borrowed lifetime sufficient
Return from function IntoStaticStr 'static lifetime needed
Custom string values AsRefStr Supports to_string attribute
Static collections IntoStaticStr 'static required for storage
Serialization Both work Choose based on ownership needs
Generic bounds AsRefStr More flexible AsRef<str> bound

Key insight: AsRefStr and IntoStaticStr serve fundamentally different ownership models. AsRefStr implements AsRef<str>, borrowing from the enum instance—this supports custom string representations via to_string but requires the enum to remain in scope. IntoStaticStr implements Into<&'static str>, returning compile-time string literals from the variant names themselves—this provides 'static references that can outlive the enum but ignores any custom to_string attributes. Choose AsRefStr when you need custom strings or when borrowing is acceptable. Choose IntoStaticStr when you need 'static references for storage, return values, or static contexts, and when variant names are acceptable as the string representation.