๐Ÿฆ€ Functional Rust

475: String Building Patterns

Difficulty: 1 Level: Beginner Build strings incrementally and efficiently โ€” the right tool for each situation.

The Problem This Solves

In Python, building a string with `+=` in a loop is discouraged because each `+=` creates a new string object โ€” O(nยฒ) copies. Instead you collect into a list and call `"".join(parts)`. JavaScript has the same issue; the fix is `Array.prototype.join` or template literals. Rust has the same concern, but with more explicit control. `String` is a growable heap buffer. You can append to it with `.push_str()` and `.push()`. You can pre-allocate with `String::with_capacity()` to avoid reallocations. And since strings are just iterators of chars, you can collect directly from an iterator โ€” which is often the most idiomatic Rust approach. Knowing which tool to use matters for both performance and readability. `format!()` is clear but allocates. `push_str` + `with_capacity` is fastest for large builds. `join` is perfect for slice-of-strings. `collect()` is idiomatic when you're already working with an iterator.

The Intuition

Think of `String` as `Vec<char>` (roughly). You can push to the end cheaply when there's capacity. `String::with_capacity(n)` pre-allocates `n` bytes on the heap โ€” like Python's `bytearray` with a pre-set size. Then `.push_str()` appends without reallocating, as long as you stay within capacity. OCaml's equivalent is `Buffer.create` + `Buffer.add_string` โ€” Rust's `String::with_capacity` + `push_str` is the same pattern. For joining a list of strings, both languages have a direct equivalent: OCaml's `String.concat sep list` = Rust's `slice.join(sep)`. For iterator-based building: any iterator that yields `char` or `&str` can be `.collect::<String>()`. This is Rust's most idiomatic approach โ€” transform with iterator adapters, collect at the end.

How It Works in Rust

// push_str / push โ€” append to a String
let mut s = String::new();
s.push_str("Hello");   // append a &str
s.push(',');           // append a single char
s.push(' ');
s.push_str("World!");

// with_capacity โ€” avoid reallocations for known-size builds
let mut buf = String::with_capacity(64);
for i in 0..5 {
 buf.push_str(&i.to_string());
 if i < 4 { buf.push(','); }
}
// buf = "0,1,2,3,4", no reallocations if total < 64 bytes

// join โ€” cleanest for &[&str] or &[String]
let words = ["the", "quick", "brown", "fox"];
let sentence = words.join(" ");  // "the quick brown fox"

// collect from iterator โ€” most idiomatic
let upper: String = "hello"
 .chars()
 .map(|c| c.to_ascii_uppercase())
 .collect();  // "HELLO"

// repeat
let divider = "โ”€".repeat(40);

// Combine with iterator: produce Vec<String>, then join
let csv: String = (1..=5)
 .map(|n| n.to_string())
 .collect::<Vec<_>>()
 .join(", ");  // "1, 2, 3, 4, 5"

What This Unlocks

Key Differences

ConceptOCamlRust
Mutable string builder`Buffer.create n``String::with_capacity(n)`
Append string`Buffer.add_string buf s``s.push_str(other)`
Append char`Buffer.add_char buf c``s.push(c)`
Extract result`Buffer.contents buf``buf` (already a `String`)
Join with separator`String.concat sep list``slice.join(sep)`
Build from iterator`String.concat "" list``.collect::<String>()`
Repeat string`String.concat "" (List.init n ...)``"x".repeat(n)`
// 475. String building patterns
fn main() {
    // push_str / push
    let mut s = String::new();
    s.push_str("Hello"); s.push(','); s.push(' '); s.push_str("World!");
    println!("{}", s);

    // with_capacity โ€” pre-allocate
    let mut buf = String::with_capacity(64);
    for i in 0..5 { buf.push_str(&i.to_string()); if i<4 { buf.push(','); } }
    println!("{} (len={} cap>=64:{})", buf, buf.len(), buf.capacity()>=5);

    // join
    let words = ["the","quick","brown","fox"];
    println!("{}", words.join(" "));

    // collect from chars
    let upper: String = "hello".chars().map(|c| c.to_ascii_uppercase()).collect();
    println!("{}", upper);

    // repeat
    println!("{}", "ab".repeat(4));

    // extend
    let mut s2 = String::from("nums: ");
    let parts: Vec<String> = (1..=5).map(|n| n.to_string()).collect();
    s2.push_str(&parts.join(", "));
    println!("{}", s2);
}

#[cfg(test)]
mod tests {
    #[test] fn test_push()     { let mut s=String::new(); s.push_str("hi"); s.push('!'); assert_eq!(s,"hi!"); }
    #[test] fn test_join()     { assert_eq!(vec!["a","b","c"].join("-"),"a-b-c"); }
    #[test] fn test_collect()  { let s:String="abc".chars().rev().collect(); assert_eq!(s,"cba"); }
    #[test] fn test_repeat()   { assert_eq!("ha".repeat(3),"hahaha"); }
    #[test] fn test_capacity() { let s=String::with_capacity(100); assert!(s.capacity()>=100); }
}
(* 475. String building โ€“ OCaml *)
let () =
  let buf = Buffer.create 64 in
  Buffer.add_string buf "Hello";
  Buffer.add_char buf ',';
  Buffer.add_string buf " World!";
  Printf.printf "%s\n" (Buffer.contents buf);

  let words = ["the";"quick";"brown";"fox"] in
  Printf.printf "%s\n" (String.concat " " words);

  let chars = ['H';'e';'l';'l';'o'] in
  let s = String.init (List.length chars) (List.nth chars) in
  Printf.printf "%s\n" s;

  let repeated = String.concat "" (List.init 4 (fun _ -> "ab")) in
  Printf.printf "%s\n" repeated