String Concatenation
Functional Programming
Tutorial
The Problem
String concatenation appears everywhere: building HTTP responses, constructing file paths, assembling SQL queries. The naive approach of a + b + c + ... allocates a new string at every +. A good concatenation strategy should: reuse existing allocations when possible, handle N parts efficiently, and not require transferring ownership unnecessarily. Understanding the trade-offs between the four Rust strategies prevents O(N²) allocation bugs in hot code paths.
🎯 Learning Outcomes
+ to append a &str to a String (moves the left side, no extra allocation)format! when mixing types or needing complex formatting (always allocates).join(separator) for joining a slice of strings efficiently&str into a String via FromIteratorString::with_capacity when the final size is knownCode Example
#![allow(clippy::all)]
// 485. Efficient string concatenation
#[cfg(test)]
mod tests {
#[test]
fn test_add() {
let a = String::from("hi");
let b = String::from("!");
let s = a + &b;
assert_eq!(s, "hi!");
}
#[test]
fn test_join() {
assert_eq!(vec!["a", "b", "c"].join("-"), "a-b-c");
}
#[test]
fn test_format() {
assert_eq!(format!("{}-{}", 1, 2), "1-2");
}
#[test]
fn test_collect() {
let s: String = vec!["a", "b", "c"].join("");
assert_eq!(s, "abc");
}
}Key Differences
+**: Rust's a + &b moves a into the result, reusing its allocation; OCaml's a ^ b always creates a new string.format! cost**: Both Rust's format! and OCaml's Printf.sprintf always allocate; but Rust's write!(buf, ...) amortises over a pre-allocated String.Iterator<Item=&str> directly into String via FromIterator; OCaml needs String.concat "" (List.map ...).Buffer vs. with_capacity**: OCaml's Buffer is a dedicated mutable builder; Rust's String is its own builder via push_str with optional with_capacity.OCaml Approach
OCaml's ^ operator always allocates a new string:
"hi" ^ "!" (* new allocation *)
For efficient multi-part concatenation, Buffer is the idiom:
let buf = Buffer.create 16 in
Buffer.add_string buf "hi";
Buffer.add_string buf "!";
Buffer.contents buf
String.concat "-" ["a";"b";"c"] is the equivalent of .join. OCaml has no + move semantics; all strings are immutable.
Full Source
#![allow(clippy::all)]
// 485. Efficient string concatenation
#[cfg(test)]
mod tests {
#[test]
fn test_add() {
let a = String::from("hi");
let b = String::from("!");
let s = a + &b;
assert_eq!(s, "hi!");
}
#[test]
fn test_join() {
assert_eq!(vec!["a", "b", "c"].join("-"), "a-b-c");
}
#[test]
fn test_format() {
assert_eq!(format!("{}-{}", 1, 2), "1-2");
}
#[test]
fn test_collect() {
let s: String = vec!["a", "b", "c"].join("");
assert_eq!(s, "abc");
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
#[test]
fn test_add() {
let a = String::from("hi");
let b = String::from("!");
let s = a + &b;
assert_eq!(s, "hi!");
}
#[test]
fn test_join() {
assert_eq!(vec!["a", "b", "c"].join("-"), "a-b-c");
}
#[test]
fn test_format() {
assert_eq!(format!("{}-{}", 1, 2), "1-2");
}
#[test]
fn test_collect() {
let s: String = vec!["a", "b", "c"].join("");
assert_eq!(s, "abc");
}
}
Exercises
criterion to compare +, format!, join, and push_str for concatenating 100 strings of 10 chars each.join_path(components: &[&str]) -> String using String::with_capacity (pre-calculate the exact capacity) and push_str.interleave(parts: &[&str], sep: &str) -> String equivalent to .join but skipping empty parts, using filter + collect on an iterator.