Table of contents
String literals (&str) in Rust are handled efficiently, with distinct memory characteristics compared to heap-allocated String types. Understanding their allocation and lifetime is key to writing performant and safe Rust code.
String Literals (&str) in Memory
Storage Location
- String literals (e.g.,
"hello") are stored in the read-only data segment (.rodata) of the compiled binary, not on the heap or stack. - They are embedded directly in the executable and loaded into memory at program startup.
- Memory is static, meaning it lives for the entire program duration.
Type Inference
- The type of
"hello"is&'static str:&str: An immutable string slice.'static: A lifetime lasting the entire program.
Example: Memory Layout:
let s: &'static str = "hello"; // Points to static memory
- Binary Representation:
- Executable Memory:
"hello"stored in.rodatasection, e.g., at address0x1000. - Variable
s: A pointer (0x1000) + length (5), stored on the stack.
- Executable Memory:
Key Properties
| Property | Explanation |
|---|---|
| Immutable | Cannot modify the literal (e.g., "hello"[0] = 'H' is forbidden). |
| Zero-Cost | No runtime allocation (already in memory). |
| Lifetime | Always 'static (valid for the whole program). |
Comparison with String
| Feature | &'static str (literal) | String |
|---|---|---|
| Memory Location | Read-only data segment | Heap |
| Mutability | Immutable | Mutable |
| Lifetime | 'static |
Scoped to owner |
| Allocation Cost | None (compile-time) | Runtime allocation |
Common Use Cases
Constants
const GREETING: &str = "hello"; // No allocation
Function Arguments
Prefer &str over &String to accept literals without allocation:
fn print(s: &str) { /* ... */ }
print("world"); // No conversion needed
Why Not Always Use &'static str?
- Limited to compile-time-known strings.
- Cannot dynamically create or modify them (unlike
String).
Example: Dynamic Strings Require String:
let name = "Alice".to_string(); // Heap-allocated copy
name.push_str(" and Bob"); // Mutability possible
The Problem: Dangling Pointer Risk
Returning a reference (&str) to a local String creates a dangling pointer, as the String is dropped when the function ends.
Example: Code That Fails to Compile:
fn return_str() -> &str { // ERROR: Missing lifetime specifier!
let s = String::from("hello");
&s // Returns a reference to `s`...
} // `s` is dropped here (dangling pointer!)
Compiler Error:
error[E0106]: missing lifetime specifier
--> src/main.rs:1:17
|
1 | fn return_str() -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
Why Rust Rejects This
- Ownership Rules:
String(s) is owned by the function and dropped when the scope ends. Returning&swould create a reference to freed memory. - Lifetime Enforcement: Rust requires explicit lifetimes to ensure references are always valid. Here, the reference (
&str) has no owner to borrow from after the function exits.
How to Fix It
There are exactly three shapes the fix can take, and they differ in who owns the bytes:
Option 1: Return an Owned String (No Reference)
fn return_owned() -> String { // Transfer ownership to caller
String::from("hello") // No reference, no lifetime issue
}
Option 2: Return a &'static str (String Literal)
fn return_static() -> &'static str { // Lives forever in binary
"hello" // Static memory (not heap)
}
Option 3: Use Cow<str> for Flexibility
use std::borrow::Cow;
fn return_cow(is_heap: bool) -> Cow<'static, str> {
if is_heap {
Cow::Owned(String::from("hello")) // Heap-allocated
} else {
Cow::Borrowed("hello") // Static memory
}
}
Key Takeaways
✅ String literals:
- Live in static memory (part of the binary).
- Are immutable and zero-cost.
- Have
'staticlifetime.
🚀 When to use them:
- For fixed, read-only strings (e.g., messages, constants).
- To avoid allocations in function APIs (
&strover&String).
✅ Never return &str borrowed from a local String—it’s impossible in safe Rust.
✅ Solutions:
- Return
String(ownership transfer). - Use
&'static str(literals only). - Use
Cow<str>for dynamic choices.
Advanced Note: Rust optimizes &str references to literals. Even if you write:
let s = String::from("hello");
let slice = &s[..]; // Points to heap, not static memory!
The compiler may elide copies if the content is known statically.
Experiment: What happens if you try returning &s[..] instead of &s?
Answer: No—it’s the same issue! The slice still points to the doomed String.