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?