🎓 A1Learner
🔍
0 of 142 lessons completed

Rust Stack & Heap

The stack is a pile of fixed-size values, where the last value put on is the first one taken off. The heap is space a program asks for while it runs, and it holds values whose size can still change.

Some text has to grow

Programs build text piece by piece. A greeting gains a name, then a whole sentence. The final size is known only while the program runs.

let mut text = String::from("hi");
println!("{} letters", text.len());
text.push_str(" there");
println!("{} letters", text.len());
text.push_str(", world");
println!("{} letters", text.len());

The letters live on the heap

Text that can grow lives on the heap. Asking the heap for space is called allocating. A String keeps a small fixed part on the stack, called the handle. The handle holds a pointer, a length and a capacity. A pointer is the address of the letters, and the capacity is the room reserved for more.

let s = String::from("hi");
println!("handle = {} bytes", std::mem::size_of_val(&s));
println!("letters = {}, room for {}", s.len(), s.capacity());

Capacity runs ahead of length

The room grows in jumps, not letter by letter. The capacity leaps from 8 to 16 the moment the text outgrows it. Between jumps, new letters move into room that is already waiting.

let mut s = String::new();
s.push('a');
println!("len = {} capacity = {}", s.len(), s.capacity());
s.push_str("bcdefgh");
println!("len = {} capacity = {}", s.len(), s.capacity());
s.push('i');
println!("len = {} capacity = {}", s.len(), s.capacity());
s.push_str("jklmnop");
println!("len = {} capacity = {}", s.len(), s.capacity());

Last in, first out

Each call adds its values on top of the pile. The call below cannot finish until the call above it has finished. down calls itself with a smaller number each time. The end lines print in reverse order, because the last call in is the first call out.

fn down(n: i32) {
    if n == 0 {
        return;
    }
    println!("start {n}");
    down(n - 1);
    println!("end {n}");
}
fn main() {
    down(3);
}

Example

fn main() {
    let n = 42;
    let mut s = String::from("hi");
    println!("n = {n}, size = {} bytes", std::mem::size_of_val(&n));
    println!("handle size = {} bytes", std::mem::size_of_val(&s));
    s.push_str(" there, world");
    println!("s = {s}, letters = {}", s.len());
    println!("handle size = {} bytes", std::mem::size_of_val(&s));
}

Key points

  • The stack is fast to use, because the next space is always the top of the pile, with no searching.
  • A value can sit on the stack only when its size is fixed and known before the program runs.
  • Heap data is a little slower to reach, because the program follows the pointer to the address first.
  • When a call returns, its values leave the stack at once, so the stack cleans itself as the program runs.

In this example, n is a whole number. Its size is fixed at 4 bytes, so it sits on the stack. s is a String. Its handle prints at 24 bytes, and after the push it prints 24 bytes again. Only the letters changed. They grew from 2 to 15 on the heap. A String is a small fixed handle on the stack, and its letters live and grow on the heap. A later lesson, Ownership, shows the rule that frees heap memory at exactly the right moment.

Note

The stack is Rust's default home, and a value uses the heap only when its type asks for space there, as String does.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why does a String keep its letters on the heap instead of the stack?