String Manipulation
Build and transform strings with push, push_str, +, format!, and capacity control.
Recipe
fn join_parts(parts: &[&str]) -> String {
let mut out = String::new();
for (i, p) in parts.iter().enumerate() {
if i > 0 { out.push(' '); }
out.push_str(p);
}
out
}When to reach for this: Assembling output, normalizing text, builder patterns.
Working Example
fn with_capacity_join(items: &[&str]) -> String {
let len: usize = items.iter().map(|s| s.len()).sum::<usize>() + items.len();
let mut s = String::with_capacity(len);
for (i, item) in items.iter().enumerate() {
if i > 0 { s.push(','); }
s.push_str(item);
}
s
}What this demonstrates:
with_capacityreduces realloc- In-place building vs
format!in loop +consumes leftString- preferpush_str
Deep Dive
insert, insert_str, replace, replacen, drain, clear, truncate (byte-based!). split, lines, trim.
Gotchas
s1 + &s2moves s1 - Left operand consumed. Fix:format!("{s1}{s2}")orpush_str.- truncate splits codepoints - Invalid if mid-char. Fix: Char-aware truncation.
- Repeated format! in loop - Allocations. Fix: Single buffer
write!orpush_str. - replace allocates - New string each call. Fix: In-place if pattern rare.
- clear retains capacity - Good for reuse buffers.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
format! | Few joins | Hot loop builder |
write! to String | fmt traits | Simple concat |
itertools::join | Iterator join | Already have Vec |
FAQs
capacity?
len vs capacity methods.shrink_to_fit?
Return excess alloc.insert_str?
Byte index - char boundary!pop?
Removes last char not byte.split_whitespace?
Iterator &str parts.to_lowercase?
Allocates new String.ascii methods?
to_ascii_uppercase etc.bytes mut?
as_mut_vec unsafe pattern avoid.serde?
String transparent.smallstr?
Inline string crates.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.