Stack and Heap
The stack and the heap are the two primary regions of runtime memory available to a Rust program. All values in Rust are allocated on the stack by default unless explicitly placed on the heap through an allocator-backed container such as Box<T>, Vec<T>, or String.
Stack Memory
The stack is a contiguous, thread-local block of memory managed in last-in, first-out (LIFO) order. When a function is called, the CPU reserves a stack frame by advancing the stack pointer register (rsp on x86_64, sp on AArch64) by a compile-time known offset. When the function returns, the stack pointer moves back, reclaiming the entire frame in a single instruction.
Every value stored directly on the stack must implement the Sized trait so the compiler knows the exact frame size before execution begins.
Heap Memory
The heap is a process-wide pool of dynamically managed memory shared across threads. Placing data on the heap requires requesting a block of a specified size and alignment from the global allocator (std::alloc::GlobalAlloc). The allocator searches for an available region, marks it as in use, and returns a raw pointer to its starting address.
When the owning stack handle (Box<T>, Vec<T>, Arc<T>) goes out of scope, its Drop implementation passes the pointer and layout back to the allocator to free the heap block.
Stack Frame (main) Heap
+---------------------------+ +---+---+---+---+
| fixed: [i32; 3] | | 10| 20| 30| 40|
| [0] = 1 | +---+---+---+---+
| [1] = 2 | ^
| [2] = 3 | |
+---------------------------+ |
| dynamic: Vec<i32> | |
| ptr --------------------+-------------+
| len = 4 |
| cap = 4 |
+---------------------------+
Comparing Stack and Heap Characteristics
| Characteristic | Stack | Heap |
|---|---|---|
| Allocation mechanism | Stack pointer bump (1 CPU instruction) | Allocator search and bookkeeping |
| Deallocation mechanism | Stack pointer restoration on return | Explicit deallocation via Drop |
| Type size requirement | Compile-time known (Sized) |
Runtime size supported |
| Memory locality | Contiguous, high L1 cache hit rate | Potentially fragmented across pages |
| Lifetime bound | Tied to lexical call frame | Arbitrary; can outlive creating function |
| Capacity limit | Fixed per thread (8 MiB default on main thread, 2 MiB on spawned threads) |
Bounded by available virtual/physical RAM |
Inspecting Stack and Heap Addresses
Local variables reside at high virtual memory addresses inside the thread stack, while heap allocations reside in lower address ranges managed by the system allocator.
fn main() {
let stack_val: u64 = 42;
let heap_val: Box<u64> = Box::new(42);
let vec_val: Vec<u64> = vec![1, 2, 3, 4];
println!("Address of stack_val: {:p}", &stack_val);
println!("Address of heap_val handle: {:p}", &heap_val);
println!("Address of *heap_val data: {:p}", &*heap_val);
println!("Address of vec_val handle: {:p}", &vec_val);
println!("Address of vec_val buffer: {:p}", vec_val.as_ptr());
}
Address of stack_val: 0x16fdfee88
Address of heap_val handle: 0x16fdfee90
Address of *heap_val data: 0x600001794040
Address of vec_val handle: 0x16fdfee98
Address of vec_val buffer: 0x600001798020
Moving Between Stack and Heap
Values move from the stack to the heap via Box::new, and move out of a Box<T> back onto the stack by dereferencing *boxed_value.
fn main() {
let on_stack = [1_u32, 2, 3, 4];
let on_heap: Box<[u32; 4]> = Box::new(on_stack);
let back_on_stack: [u32; 4] = *on_heap;
println!("back_on_stack: {:?}", back_on_stack);
}
back_on_stack: [1, 2, 3, 4]