Strings
Rust String Types Cheat Sheet
| Type | Memory Location | Size on Stack | Ownership | Reallocation / Mutation Cost |
|---|---|---|---|---|
&'static str |
Read-Only Data Segment (.rodata) |
16 bytes (ptr + len) |
Borrowed indefinitely; zero allocation | Immutable; cannot grow in place |
String |
Heap (buffer) + Stack (fat pointer) | 24 bytes (ptr + cap + len) |
Owned buffer; drops memory on scope exit | Can grow in place; reallocates when len > cap |
Cow<'static, str> |
Stack enum (Borrowed vs Owned) |
24 bytes (enum discriminant + data payload) |
Lazily owned; borrows until mutation or ownership is required | Zero-cost until .to_mut() triggers an allocation |
Mechanical Breakdown & Usage
1. &'static str
An immutable byte slice pointing to memory embedded directly in the compiled binary. It incurs zero runtime allocation cost and cannot be dropped.
- Use case: Hardcoded UI labels, static error codes, system constants.
- Avoid when: Values are dynamically parsed or constructed at runtime.
2. String
A growable, UTF-8 encoded heap buffer managed by a 3-word struct on the stack.
- Use case: Dynamic user inputs, JSON payload parsing, runtime string mutation.
- Avoid when: Passing read-only slices into functions where borrowing suffices.
3. Cow<'static, str>
A clone-on-write smart pointer defined as enum Cow<'a, B> { Borrowed(&'a B), Owned(<B ToOwned as>::Owned) }. It acts as an optimization layer avoiding heap allocations when processing static defaults, switching to an owned String only when mutation or dynamic data is present.
use Cow;
- Use case: Config registries with static fallbacks, UI element labels that are usually static strings but occasionally formatted dynamically.
- Avoid when: Data is guaranteed to always be dynamically allocated (use
String) or guaranteed to always be static (use&'static str).
Generic Functions for Zero-Allocation & Flexible APIs
Accepting String in function arguments forces callers with string literals to call .to_string(), triggering unnecessary malloc/memcpy cycles.
Use standard library abstraction traits depending on whether the function requires read-only inspection, ownership, or conditional mutation.
Pattern A: Read-Only Access via AsRef<str>
Zero heap allocation. Accepts &str, &'static str, String, Cow<str>, and &String without taking ownership.
// Caller ergonomics:
log_component_event; // &'static str (0 allocs)
log_component_event; // String (1 alloc from format)
Pattern B: Conversion to Owned Storage via Into<String>
When a struct or collection must own the data, take Into<String>. Callers passing an existing String yield ownership directly without cloning; callers passing a string literal pay for exactly one allocation at the call site.
// Caller ergonomics:
let c1 = new; // Allocates String only on entry
let owned = Stringfrom;
let c2 = new; // Zero reallocation; takes ownership of existing buffer
Pattern C: Maximizing Cache/Memory Efficiency via Into<Cow<'a, str>>
Optimal for structs that can hold either a static slice or an owned string, completely bypassing the heap when initialized with string literals while remaining compatible with dynamic data.
use Cow;
// Caller ergonomics:
// 1. Literal path: Zero heap allocations, points directly to binary's data segment
let static_node = new;
assert!;
// 2. Dynamic path: Takes ownership of the heap buffer seamlessly
let dynamic_node = new;
assert!;