ExamplesBy LevelBy TopicLearning Paths
485 Fundamental

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

  • • Use + to append a &str to a String (moves the left side, no extra allocation)
  • • Use format! when mixing types or needing complex formatting (always allocates)
  • • Use .join(separator) for joining a slice of strings efficiently
  • • Collect from an iterator of &str into a String via FromIterator
  • • Pre-allocate with String::with_capacity when the final size is known
  • Code 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

  • **Move semantics in +**: 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 collect: Rust collects 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

  • Benchmark four strategies: Use criterion to compare +, format!, join, and push_str for concatenating 100 strings of 10 chars each.
  • Allocation-free path builder: Implement join_path(components: &[&str]) -> String using String::with_capacity (pre-calculate the exact capacity) and push_str.
  • Interleave: Write interleave(parts: &[&str], sep: &str) -> String equivalent to .join but skipping empty parts, using filter + collect on an iterator.
  • Open Source Repos