Skip to content

Stack and Heap

The stack stores values in order and removes them in reverse: last in, first out. Pushing is just moving the top pointer, so it is fast. Everything on the stack has a known fixed size. Locals live here and are freed when the function returns. Allocations hoist to the function entry, so loops never grow the stack:

fn main() {
let num = 123;
let other = 321;
}

Each new value pushes on top, popping goes in reverse:

STACK
┌─────────┐ ┌────────────────┐
│ 321 │◄─────────│let other = 321;│
├─────────┤ └────────────────┘
│ 123 │◄────────┐┌──────────────┐
├─────────┤ └│let num = 123;│
│ ... │ └──────────────┘
├─────────┤
│ ... │
└─────────┘

Data with unknown or changing size lives on the heap. The allocator finds a free spot, marks it used and returns a pointer to it. The pointer itself is fixed-size and sits on the stack, the data follows it. Allocating costs more than pushing: the allocator has to search for a free spot and keep bookkeeping.

Heap goes through Allocator: alloc panics on null, dealloc frees:

use std.alloc;
fn main() {
let block = Allocator.alloc(4);
}

The pointer sits on the stack, the data on the heap:

STACK
┌─────────┐ ┌──────────────────────────────┐
┌──┤0xADDRESS│◄─────────│let block = Allocator.alloc(4)│
│ ├─────────┤ └──────────────────────────────┘
│ │ ... │
│ ├─────────┤
│ │ ... │
│ ├─────────┤
│ │ ... │
│ └─────────┘
▼
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ │ │ │░│░│░│░│ │ │ │ │ │ │
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘