Here is how String source code look like:

#[derive(PartialEq, PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
#[lang = "String"]
pub struct String {
    vec: Vec<u8>,
}

In Rust, String is implemented as a wrapper around a Vec<u8> because it is designed to be a dynamically growable, heap-allocated buffer of UTF-8 encoded bytes. The standard library leverages the existing Vec type to manage the string’s underlying memory while enforcing specific character encoding rules.

The Stack Layout

When you create a String, Rust places a small, fixed-size struct on the stack. On a 64-bit system, this struct is exactly 24 bytes and consists of three machine-word fields:

  • Pointer: A reference pointing to the memory address on the heap where the actual UTF-8 text data is stored.

  • Length: The amount of memory (in bytes) currently being used by the string’s text.

  • Capacity: The total amount of heap memory currently allocated for the string.

Because this control block is small, moving a String from one variable to another is incredibly fast. Rust only copies these 24 bytes of metadata on the stack rather than copying the actual text data on the heap.

String memory allocation

  • p is the address of the String object itself, so text keeps the same p the whole time because it is the same variable.

  • ptr is the address of the actual character buffer.

  • len is the current string size.

  • capacity is how much buffer space is available before another growth is needed.

fn main() {
    let mut text = String::new();
    println!("p: {:p} ptr: {:<15?} len: {:2}, capacity: {:2} '{}'", &text, text.as_ptr(), text.len(), text.capacity(), text);
 
    text.push_str("Hello foo!");
    println!("p: {:p} ptr: {:<15?} len: {:2}, capacity: {:2} '{}'", &text, text.as_ptr(), text.len(), text.capacity(), text);
 
    text.replace_range(6..9, "bar");
    println!("p: {:p} ptr: {:<15?} len: {:2}, capacity: {:2} '{}'", &text, text.as_ptr(), text.len(), text.capacity(), text);
 
    let temp = String::from("The black cat");
    println!("{temp}");
    println!("p: {:p} ptr: {:<15?} len: {:2}, capacity: {:2} '{}'", &temp, temp.as_ptr(), temp.len(), temp.capacity(), temp);
 
    text.replace_range(6..9, "qqrq");
    println!("p: {:p} ptr: {:<15?} len: {:2}, capacity: {:2} '{}'", &text, text.as_ptr(), text.len(), text.capacity(), text);
 
    text.replace_range(6..9, "123456");
    println!("p: {:p} ptr: {:<15?} len: {:2}, capacity: {:2} '{}'", &text, text.as_ptr(), text.len(), text.capacity(), text);
 
    text.replace_range(6..9, "12345678901234567890");
    println!("p: {:p} ptr: {:<15?} len: {:2}, capacity: {:2} '{}'", &text, text.as_ptr(), text.len(), text.capacity(), text);
 
}
p: 0x7ffdcc572770 ptr: 0x1             len:  0, capacity:  0 ''
p: 0x7ffdcc572770 ptr: 0x5de087754ba0  len: 10, capacity: 10 'Hello foo!'
p: 0x7ffdcc572770 ptr: 0x5de087754ba0  len: 10, capacity: 10 'Hello bar!'
The black cat
p: 0x7ffdcc573260 ptr: 0x5de087754bc0  len: 13, capacity: 13 'The black cat'
p: 0x7ffdcc572770 ptr: 0x5de087754ba0  len: 11, capacity: 20 'Hello qqrq!'
p: 0x7ffdcc572770 ptr: 0x5de087754ba0  len: 14, capacity: 20 'Hello 123456q!'
p: 0x7ffdcc572770 ptr: 0x5de087754be0  len: 28, capacity: 40 'Hello 12345678901234567890q!'

String and memory allocation