005 - String, &str, and Allocation Patterns
String, &str, and Allocation Patterns
Why this matters
A lot of Rust performance and ergonomics comes down to one practical question: should this API take String or &str?
Learning goals
By the end, you should be able to:
- Explain the difference between
Stringand&str. - Design APIs that avoid unnecessary allocations.
- Use
Cow<'_, str>when you sometimes need owned data.
Mental model
String= owned, growable UTF-8 buffer (heap-allocated).&str= borrowed string slice, view into UTF-8 bytes.- Prefer borrowing (
&str) at API boundaries unless ownership is needed.
Python baseline
def normalize(name: str) -> str:
return name.strip().lower()
Python strings are immutable and reference-managed; copying behavior is runtime-managed.
Rust examples
fn normalize(name: &str) -> String {
name.trim().to_lowercase()
}
fn print_title(title: &str) {
println!("{}", title);
}
Both &String and string literals (&'static str) can be passed as &str.
Runnable end-to-end example
use std::borrow::Cow;
fn normalize(name: &str) -> String {
name.trim().to_lowercase()
}
fn maybe_trim(input: &str) -> Cow<'_, str> {
let trimmed = input.trim();
if trimmed.len() == input.len() {
Cow::Borrowed(input)
} else {
Cow::Owned(trimmed.to_string())
}
}
fn main() {
let owned = String::from(" Rustacean ");
let literal = "Pythonista";
// borrow both as &str
println!("normalized owned: {}", normalize(&owned));
println!("normalized literal: {}", normalize(literal));
// avoid allocation when unchanged
let a = maybe_trim("no_spaces");
let b = maybe_trim(" has spaces ");
println!("a = {} (borrowed: {})", a, matches!(a, Cow::Borrowed(_)));
println!("b = {} (borrowed: {})", b, matches!(b, Cow::Borrowed(_)));
}
Common pitfalls
- Accepting
Stringwhen&strwould be enough. - Cloning strings at call sites because function signatures are too strict.
- Returning borrowed
&strtied to temporary allocations.
Quick practice
- Refactor a function from
fn f(x: String)tofn f(x: &str)and update call sites. - Add a function that concatenates two
&strvalues and returnsString.
Recap
Use &str for input flexibility and fewer allocations; use String when ownership is part of the API contract.
Next lesson: 006 - Smart Pointers (Box, Rc, Arc, RefCell).
Join the Journey Ahead!
If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.
Other articles in the series
- 000 - Memory Mastery Roadmap
- 001 - Memory Mastery: Zero-Cost Abstractions
- 002 - Stack vs Heap, Moves, and Clones
- 003 - Borrowing Rules in Real Functions
- 004 - Lifetimes Without Fear
- 006 - Smart Pointers (`Box`, `Rc`, `Arc`, `RefCell`)
- 007 - Interior Mutability and Tradeoffs
- 008 - Data Layout and Cache-Friendly Rust
- 009 - Concurrency Memory Safety (`Send`, `Sync`)
- 010 - Profiling and Benchmarking Rust vs Python