Goose: A Systems Language With No Heap, No GC — and No Lifetime Annotations
By Arash Latifi
How the new Goose language achieves memory safety using data stacks instead of a heap, and why its benchmarks claim it beats C++ and Rust
TL;DR — Goose manages memory with data stacks instead of a heap: no GC, no allocator, not even lifetime annotations — and its own benchmarks claim it runs faster than C++ and safe Rust. The trade-offs are just as interesting.
Where it came from
On October 24, 2026, the Goose language hit the front page of Hacker News with around 444 points — right after the Brotli CVE-2026-111497 mess put memory safety back on everyone's radar. Goose lands squarely in that conversation. It ships with a full tutorial, reference, specification, 31 sample programs, and benchmarks that include its losses.
The name is a joke; the design isn't. The core idea in one sentence: every dynamic value lives inline on a data stack, and memory is freed when its scope ends. That's the whole model.
The memory model: stacks, not a heap
A Goose program has exactly three kinds of memory: the native call stack, static data, and N data stacks whose count the compiler works out. Each stack is a large address-space reservation with a bump pointer — there is no other memory. At most one resizable value is live per stack, and it's always on top, so:
- Growth is a pointer bump: no reallocation, no copying, no capacity checks
- Freeing a million-element structure is one store
- References never move, so they survive array growth
That last point is impossible in C++ or Rust: a Vec<T> may reallocate and invalidate references, which is why safe Rust uses u32 indices in the corresponding benchmarks. In Goose, a reference to element 0 is still valid — and writable — a million pushes later.
| Aspect | C++ | Rust (safe) | Goose | |---|---|---|---| | Memory management | manual + RAII | allocator + Drop | bump stack, freed on scope exit | | Reference safety | UB possible | borrow checker + lifetimes | inferred static roots, no annotations | | Reference after array growth | dangling | usually an index | stays valid | | GC / refcounting | none | none | none |
Safety with zero annotations
The most striking part for Rust programmers: Goose has no lifetime syntax at all. No 'a, no aliasing or exclusivity rules, no unsafe. The compiler infers a static root for every reference — the variable that bounds the target's lifetime — and rejects references that could outlive their owner or access the target through an incompatible type.
Why it's fast
From the project's own results (16 benchmarks, losses included):
- 3.3x faster than idiomatic C++ with 1.9x less memory
- 1.16x faster than hand-optimized C++ with 1.3x less memory
- 1.12x faster than the best safe Rust with 1.2x less memory
The gains come from costs that are constant in heap-based languages: inline data layout (a 160-byte record that's one allocation in C++ is 29 bytes and zero allocations in Goose), variable-mode enums where each value takes its own variant's size rather than the largest one (4x less memory and 2x the speed of a Rust enum on the benchmark exercising it), and 1–4 byte relative "links" instead of pointers.
Genuinely novel bits
- Your data is already a file format: structures built from relative links are position independent — saving is a write, loading is a read plus a verification pass that rejects hostile bytes.
- Errors without an unwinder:
return err from loadreturns from any number of frames up, statically checked, with noResulttype and no?on every call. - Threads that share nothing: each worker compiles as a separate program with its own memory; flat values cross typed queues as a
memcpy. Data races, locks, and atomics don't exist in the language. - Plain C in, plain C out: the compiler emits one C file, so it runs anywhere C does, and
extern fncalls C directly. A bundled TinyCC backend compiles and runs programs in-process with no build step. - Zero-cost generics: untyped parameters are generic, function values are compile-time entities, and
xs.filter() { it > 0 }compiles to the loop it looks like, building its result directly at the destination.
The costs — read this before you get excited
Every memory model buys something by giving something up, and the docs are honest about it:
- One live resizable value per stack: if you need two large structures growing at once, the model fights you.
- Data-oriented or nothing: pointer-heavy patterns (say, densely cross-referenced graphs) fit a linear LIFO stack badly. You have to think in terms of scope exit.
- Zero ecosystem: the language is newborn; the standard library and package story are embryonic. Using it in production right now is a real risk.
- Its own benchmarks: these numbers come from 16 benchmarks selected by the project. Publishing the losses is a good sign, but "faster on my workload" is something you have to measure yourself.
Who should care
Realistically: Goose is not a production choice for you today. It's a fascinating study, a playground for memory-model design, and maybe a fit for small performance-sensitive tools. Its real value might be proving that "memory safety → GC or a heavy borrow checker" is a false binary — you can instead design a memory model that dissolves the problem. After a week spent patching Brotli out-of-bounds write CVEs in Node runtimes, that's a perspective worth an hour of your time.
- [ ] Read the specification to understand the memory model properly
- [ ] Run the tutorial and one of the 31 samples (TinyCC executes with no build step)
- [ ] Rewrite one of your own CPU-bound tools in Goose and benchmark it against the original
- [ ] Check the benchmark losses table to see where Goose loses
- [ ] Don't use it in production — not yet
FAQ
Q: Is Goose really faster than Rust?
On the project's own 16 benchmarks (which include the losses), yes — about 1.12x faster than the best safe Rust with less memory. That is not the same as maturity, ecosystem, or winning on your real workload. Treat the numbers as a starting point, not a verdict.
Q: If there's no heap and no GC, how is dynamic memory handled?
With data stacks: every dynamic value lives inline on a bump-allocated stack, and when its scope ends, the stack pointer moves back and the space is freed. The compiler determines stack count and sizes statically.
Q: Can I use it for everyday web work?
No. Goose compiles to a single C file with a direct C ABI; it's aimed at systems tools, embedded, and CPU-heavy work. There's no web ecosystem or library maturity to speak of.