Goose Has No GC: What Its Memory Model Requires Instead

In Goose, you loop over an array of variable-size elements without trouble, but indexing straight into one isn’t allowed. What else does its no-GC memory model trade away?

Goose Has No GC: What Its Memory Model Requires Instead
TLDR
  • Goose uses compiler-managed data stacks and inferred lifetimes to avoid GC and per-object deallocation.
  • The tradeoffs affect recursive state, indexing variable-size elements and escaping closures.
  • Evaluate those constraints against your data structures; the project’s benchmark suite includes both gains and losses.

Contiguous values change both allocation and cleanup

Goose, whose language design is credited to Wouter van Oortmerssen, combines familiar C- and Rust-style syntax with compiler-managed data stacks. The project explicitly describes itself as new. Its central proposition is a different storage model, rather than a shorter way to spell existing allocation patterns.

The tutorial’s string-array example stores lengths and character data together in one contiguous region. Growth advances a pointer. Leaving the enclosing scope resets it, avoiding a separate deallocation for each string. That makes the organization of nested values part of the language’s memory-management strategy.

The compiler proves lifetimes instead of asking for their syntax

The reference specification assigns static roots to references and slices. A root identifies storage that bounds the target’s lifetime. Function specialization carries that information through calls, and storing a reference requires its target to outlive the destination.

A reference to data created inside a block cannot simply survive outside that block. Omitting explicit lifetime parameters therefore does not remove the ownership question. It shifts the work of expressing and checking those relationships into the compiler.

Recursion and container access have explicit constraints

The tutorial’s tradeoffs section identifies restrictions that can require changes to an existing design.

PatternGoose requirement
Recursive callsGrowable local data cannot remain live across a recursive call. Longer-lived state must be passed in.
Variable-size array elementsThey support iteration, but direct indexing needs a separate index of references.
Closures and function pointersEscaping closures and function pointers are unavailable.

For an adoption evaluation, map those constraints to the application’s actual data: who owns it during recursion, and whether compact sequential storage can replace direct indexed access. These questions determine how much restructuring a port would need.

The 1.19× Rust comparison is a suite result

In the project’s October 16 benchmark run, clang-built Goose achieves a 1.19× geometric-mean speed ratio against the best safe Rust implementation for each of 16 large-input benchmarks. The environment is a Ryzen 9 9950X3D running Windows 11, with clang-cl 22.1.3 compiling Goose’s C output and rustc 1.98.0 compiling Rust.

The same table puts calc at 0.89×, a loss for Goose. Timings take the best of three whole-process runs after two warm-ups, including input generation and teardown.

The benchmark design distinguishes idiomatic C++ from manually optimized implementations. It asks whether compact layouts and language-level memory management can reduce the work associated with allocation and pointer-heavy data. It also predicts little benefit for flat scalar arrays. Comparing the data structures is more informative than treating a language name as a single fixed performance baseline.

Frequently asked questions

Does removing lifetime syntax remove reference checks?

No. The compiler checks roots and destination lifetimes instead of requiring explicit annotations.

Will every program run faster than Rust?

The project’s own suite includes losses. The 1.19× figure summarizes a particular environment and benchmark set.

Start an evaluation with the application’s ownership and access patterns. Those determine whether Goose’s restrictions are a useful trade.