DEV Community

Cover image for Goose claims a 29-byte record and 3.3x over C++. I measured both.
frank chu
frank chu

Posted on

Goose claims a 29-byte record and 3.3x over C++. I measured both.

Goose is a new systems language from Wouter van Oortmerssen (the FlatBuffers author) that hit the top of Hacker News today, and its homepage does not do modest. It claims, in order: memory safety with no heap allocator, no garbage collector and no lifetime annotations; a database record that takes 29 bytes in Goose versus 160 bytes and an allocation in C++; and, over sixteen benchmarks, 3.3x the speed of idiomatic C++ and 1.16x hand-optimized C++, on less memory.

Language homepages say things. What I liked about this one is that several of its claims are small, exact and checkable in an afternoon. So I downloaded the current release (v2026.3, October 21; the repo is at 1,950 stars) and checked the small ones first.

My setup: the prebuilt Linux binary on a 2-vCPU AMD EPYC VM, gcc 13.3. Goose compiles to a single C file; goose run instead compiles in-process with a bundled TinyCC backend, which is why every run below includes compile time and still finishes in tens of milliseconds.

The 29-byte record is exactly 29 bytes

The homepage's example is an Order — an id, a customer name, and a list of items, each with a SKU string, a quantity and a price. In Goose, variable-size fields sit inline behind varint lengths, so the whole record is one contiguous run of bytes with no pointers in it. I typed in the homepage's exact example and measured the payload:

struct Item  { sku: u8[varint], qty: varint, cents: varint }
struct Order { id: varint, customer: u8[varint], items: Item[varint] }

// book.push(Order { id: 1001, customer: "alice",
//                   items: [Item { sku: "SKU-441", qty: 2, cents: 1999 },
//                           Item { sku: "SKU-7", qty: 1, cents: 500 }] })
print(book.bytes_of().len);
Enter fullscreen mode Exit fullscreen mode
one order in book: payload bytes = 29
framed to_bytes len = 30
Enter fullscreen mode Exit fullscreen mode
  1. Not "about 29" — the claim survives contact with the compiler byte for byte. (The framed form, with its length prefix, is 30.)

The enum claim checks out the same way. A variable-mode ADT stores each value at its own variant's size — the docs say a Circle (one f64) costs 9 bytes, a Rect (two) 17, a Dot 1, instead of 17 each in fixed mode:

variable-mode 3 shapes payload = 27 bytes   (9 + 17 + 1)
fixed-mode    3 shapes payload = 51 bytes   (17 x 3)
Enter fullscreen mode Exit fullscreen mode

The lifetime error is the one in the docs

Goose has no lifetime annotations; the compiler infers what every reference is rooted at and rejects a reference that could outlive its root. The homepage shows a specific failing program — storing the result of longest(outer, inner) into a variable rooted at outer, from inside the block where inner dies. I fed the compiler exactly that program:

lifetime.goose:11: error: storing a reference rooted at inner,
which does not outlive the destination (§9.2)
        w = longest(outer, inner);
Enter fullscreen mode Exit fullscreen mode

Word for word the documented error, pointing at the documented line, with the spec section attached — and goose check exits 1 on it. The flip side of the same rule is that references into a grow-only array survive growth, which in C++ would be a reallocation bug: the bundled sample pushes 100,000 items, mutates the first one afterwards through a reference taken before the pushes, and prints 100000 items, first weight 99.5 — the mutation landed.

A data structure that is its own file format

Because links inside these structures are relative offsets rather than addresses, a whole structure serializes by writing its bytes, and loads back with a verification pass, from_bytes, that rejects hostile input. The bundled serialize sample builds a 262-word index out of Alice in Wonderland and then attacks its own file. My run:

image 9696 bytes = 262 nodes x 37 + 2
loaded true, 262 nodes, same size true
compact 2494 bytes for 262 nodes, reload true
tampered link false, nodes 0
edited count true, still a tree true
truncated false, extended false, empty true 0, no prefix false
verify+copy of 9696 bytes: 0.012 ms
Enter fullscreen mode Exit fullscreen mode

Every line of that is a claim being kept, including the honest one in the middle: flip a byte inside a link and the image is rejected; flip a byte inside a count and it loads fine with the wrong number — verification is about safety, not integrity, and the sample says so in its own comments instead of hiding it.

The samples, and the one benchmark I ran myself

Of the 31 bundled samples, 26 are non-graphical; I ran those in run mode. 24 passed, 2 failed — and both failures are the tool explaining a platform limit, not crashing: the threaded Mandelbrot says TinyCC cannot place thread-local storage in an in-memory run and tells you to compile it instead, and the call-C sample fails to link because it needs its companion C file built. Compile-plus-run for most samples was 0.05–0.09 s wall clock on this VM.

For performance I only trust a comparison I ran, so here is one: the bundled sieve of Eratosthenes to 10,000,000, built natively (goose build, which emits C and calls gcc -O2), against the same byte-array sieve written by hand in C and compiled with the same gcc -O2. Nine interleaved runs each, kernel time as self-reported by each program:

goose-built   kernel ms: median 19.7   [19.7, 24.6, 20.1, 20.0, 18.4, 19.6, 18.4, 20.7, 18.5]
C, gcc -O2    kernel ms: median 19.7   [35.8, 20.1, 18.6, 18.6, 19.7, 18.6, 43.6, 18.1, 23.1]
Enter fullscreen mode Exit fullscreen mode

Identical medians. That is not a refutation of the 3.3x claim — that number is an average over sixteen benchmarks chosen to exercise Goose's data layouts (stable references, inline nested data), and the site publishes its losses too. A tight numeric loop exercises none of that, and the sieve's own source claims only that "the inner loop is the same three instructions C would emit." Parity is exactly what that predicts, and parity is what I measured. In run mode (TinyCC, -O1) the same kernel took 43–66 ms — roughly 2–3x off the compiled build, which is the honest price of not having a build step.

My take

I can't crown a language from an afternoon, and I didn't rerun their sixteen-benchmark suite. But here's the thing I did learn: the claims on this homepage that are cheap to check — a byte count, an error message, a tampered file, a sample suite — are exact, including the unflattering parts (a count edit sails through the verifier; the threads sample can't run in-process). Launch pages usually round in their own favor. This one, at least on everything I could measure, doesn't round at all. That's the strongest signal a new language can send before anyone trusts it with real work.

Sources: aardappel.github.io/goose (homepage claims, benchmark summary), github.com/aardappel/goose (repo, v2026.3 release of 2026-10-21). All runs are my own, on the release binary: the 29-byte order, the enum sizes, the lifetime error, the serialize sample, the 26-sample pass count, and the sieve comparison.

Top comments (0)