What String does

A growable, owned, UTF-8 encoded text buffer.

As a struct, String bundles related fields into a single named record with a known layout. Each field has a fixed type and offset, so the size of an instance is the sum of its field sizes plus alignment padding. Construction follows ordinary Rust ownership rules: each owned field must itself be constructed or moved before the struct is well-formed, and the compiler will refuse to leave any field uninitialised. The full canonical path is std::string::String; bring it into scope with use std::string::String; or refer to it by its full path.

When to use it

Use String for owned, mutable text. Use `&str` for borrowed text — function parameters should almost always be `&str`, and you only need a `String` when you must own or grow the buffer.

Annotated examples

Building a String with format! and push_str

fn greeting(name: &str, count: u32) -> String {
    // format! is the simplest way to build a String from a template.
    // Internally it creates a Formatter and calls write! on it.
    let mut s = format!("Hello, {name}!");
    if count > 1 {
        // push_str appends a &str to an existing String without
        // re-allocating the format machinery.
        s.push_str(&format!(" ({count} visits)"));
    }
    s
}

format! is convenient for one-shot construction; for larger buffers, allocate with String::with_capacity and use push_str / write! to avoid repeated allocations.

For deeper background, see the canonical Rust patterns reference for the broader context behind this section.

Iterating over chars (not bytes) safely

fn first_word(s: &str) -> &str {
    // chars() yields full Unicode scalar values, not bytes.
    // For ASCII-only data, bytes() is faster but indexing into
    // a String by byte offset can land in the middle of a codepoint.
    match s.find(char::is_whitespace) {
        Some(idx) => &s[..idx],
        None => s,
    }
}

String is UTF-8 — never slice a String by raw byte index unless you know the boundary is on a codepoint. find returns valid byte offsets you can slice with safely.

Common pitfalls

String slicing by byte index will panic at runtime if the index lands inside a multi-byte UTF-8 sequence — `&s[0..1]` is unsafe to assume on arbitrary strings. Use `chars().take(n).collect::<String>()` if you need the first n characters by codepoint, or `s.char_indices()` to recover safe byte boundaries for slicing.

For deeper background, see an in-depth Rust idioms cheat sheet for the broader context behind this section.

Performance & threading notes

Stack-allocated by default. Heap allocation only happens if a field is itself heap-allocated (Vec, String, Box). Cloning copies every field; for read access pass by reference. Send / Sync are automatically derived if every field is Send / Sync.