Memory is a lifetime contract
Stack or heap is not a trivia question. It is a promise about ownership, lifetime, movement, and cleanup.
- problem
- Stack-versus-heap explanations often reduce an ownership and lifetime decision to a simplistic speed comparison.
- scope
- A systems-level model of lifetime, ownership, movement, sharing, allocation, and cleanup rather than allocator microbenchmarks.
- environment
- Rust and C++ systems code where value location and ownership affect API and failure behavior.
Assumptions
- The reader already understands lexical scope and basic allocation.
Limitations
- Allocator design, escape analysis, arenas, garbage collection, and NUMA behavior need separate treatment.
Table of contents 3 sections
Compare cost shapes, not slogans
Stack values are cheap, scoped, and tied to a call frame. Heap values buy flexibility and shared reach, then invoice the system in allocation, synchronization, and ownership complexity.
The relevant cost is not a mythical single stack instruction versus a slow heap call. It includes cache locality, pointer chasing, allocator metadata, fragmentation, page faults, lifetime extension, and synchronization around shared ownership. A heap-backed Vec can still keep its elements contiguous; a large stack frame can still destroy locality or exhaust a thread stack. Placement is only one part of the data-shape decision.
Ask which lifetime you are promising
The useful question is not which one is better, but which lifetime is promised, who may move the value, who may share it, and who is accountable for cleanup.
A borrowed slice says the callee may observe data without owning its storage. Box says the value has one owning handle even though its bytes live on the heap. Arc says lifetime is distributed across owners and cleanup waits for an atomic reference count. Pin adds a location guarantee. Each type communicates a different invalidation and concurrency contract, which is why “put it on the heap” is not an architectural answer.
Make the contract visible
Systems languages make that contract visible. References, boxes, moves, and destructors are architecture—not syntax trivia.
The API should expose the narrowest valid lifetime and ownership. Accept a borrow when retention is unnecessary, return ownership when the caller must control disposal, and allocate behind an arena when many objects share one bounded phase. Measure only after the contract is correct. An allocator benchmark cannot repair an API that permits dangling references, accidental clones, unbounded retention, or cross-thread sharing without a synchronization plan.
- sourced Rust ownership model
The Rust Book makes ownership, moves, borrowing, and heap-backed smart pointers explicit in the type system.
inspect source ↗
- reference The Rust Programming Language: Ownershipopen ↗
Public reference for moves, borrowing, and ownership-oriented lifetime reasoning.