Pangram verdict · v3.3
We believe that this text is a mix of AI and human-written content.
AI likelihood · overall
AIArticle text · 1,090 words · 36 segments analyzed
A memory-safe systems language with no heap allocator, garbage collector, or lifetime annotations. In this repository's benchmarks, Goose runs faster than C++ and Rust while using less memory. Tutorial · Reference · Specification · Packages · Samples · Benchmarks · Standard library Goose has syntax familiar to C and Rust programmers, but there is no heap.
Every dynamic value lives inline on a data stack managed by the compiler. Growth advances a pointer, and memory is freed when its scope ends.
The language's type and lifetime rules make this memory model safe to use. Why Goose Faster than C++ and than safe Rust, while memory safe. Over sixteen benchmarks Goose runs at 3.3x the speed of idiomatic C++, 1.16x hand-optimized C++ and 1.12x the best safe Rust, on 1.9x, 1.3x and 1.2x less memory (results).
These gains come from Goose's data layouts and memory model.
No allocator, no GC, no reference counting, no destructors.
Memory is a handful of data stacks that the compiler assigns statically.
Freeing a million-element structure is one store, however deeply it nests.
Growth preserves references.
A reference into a growing array stays valid for as long as the array does.
You keep typed references where C++ must reserve and safe Rust uses u32 indices in the corresponding benchmarks.
Memory safe with zero annotations.
No lifetime syntax, no aliasing or exclusivity rules, no unsafe. The compiler infers what every reference is rooted in and rejects references that could outlive their owners.
Nested data stays inline.
A string, an array of strings, a record with variable-size fields and an array of those records are each one contiguous block with no pointer in it.
A record that is 160 bytes and an allocation in C++ is 29 bytes and none in Goose. Enums that cost what they hold.
Variable-mode ADTs give each value its own variant's size rather than the largest one's: 4x less memory and 2x the speed of a Rust enum on the benchmark that exercises it.
Links narrower than pointers.
A relative reference stores a typed, checked link as a 1, 2 or 4-byte offset. Structures built from them are position independent, so your data structure is already its file format: saving is a write, loading is a read plus a verification pass that rejects hostile bytes. Everything is built in place, guaranteed. A value is constructed at its final destination through any depth of calls. items.push(parse(line)) writes the parsed record straight into the array, and returning a growable array by value costs nothing.
Returns across multiple calls.
return err from load returns from a function any number of frames up, statically checked, with no unwinder, no Result type and no ?
on every call. Threads that share nothing.
A worker is compiled as a separate program with its own memory, and flat values cross typed queues as a memcpy. Data races, locks, atomics and memory orderings do not exist in the language. Generics and higher-order functions with no overhead.
An untyped parameter is generic.
Function values are compile-time entities, so xs.filter() { it > 0 } compiles to the loop it looks like and builds its result straight into its destination. Plain C in, plain C out. Goose compiles to one C file, so it runs wherever a C compiler does and calls C directly through extern fn. The bundled TinyCC backend compiles and runs a program in-process, with no build step. The tutorial walks through all of this by example, the reference covers the whole language topic by topic, the specification has the exact rules, and the benchmarks have the numbers, losses included.
Features This section focuses on what distinguishes Goose.
The tutorial explains these features in more detail, and the samples show them in thirty-one complete programs.
One memory model: stacks, and scope exit is the free A program has the native call stack, static data and N data stacks, where the compiler works out N. A data stack is a large address-space reservation with a bump pointer, and there is no other memory. At most one resizable value is live per stack and it is always on top, so growth never moves anything and never checks a capacity. All of this is proved at compile time; the runtime tracks only the bump pointers. for round in 3 { var scratch: u8[>..] = []; // grow-only: growth is a pointer bump for i in 100000 { scratch.push((i % 256) as! u8); } print("round ", round, ": ", scratch.len, " bytes"); } // the free: one store to the stack top Nothing moves, so references survive growth struct Item { id: i32, weight: f32 } var items: Item[>..] = []; let first .= items.push(Item { 1, 0.5 }); // a reference to element 0 for i: i32 in 2..1000001 { items.push(Item { i, 0.0 }); } first.weight = 99.5; // still valid, a million pushes later push returns a reference to the new element, so a program can link elements while building the array. A vector<T> or Vec<T> may reallocate and cannot provide this guarantee.
Stable references account for several benchmark gains.
Safe references, no annotations Every reference and slice has a static root: the variable that bounds its target's lifetime. A reference must not outlive the variable that owns its target or access that target through an incompatible type. The compiler infers roots and specializes functions for them, so no annotations are needed.
There are no aliasing or exclusivity rules.
When a lifetime check fails, the error names the reference's root and its destination: fn longest(a: u8[:], b: u8[:]) -> u8[:] { if a.len >= b.len { a } else { b } } var outer: u8[>..]
= []; var w = outer[..]; { var inner: u8[>..]
= []; format(inner, "inner text"); w = longest(outer, inner); // error: storing a reference rooted at inner, } // which does not outlive the destination (§9.2) A slice T[:] lets a function accept a range from any array kind without copying its elements. For example, split returns slices into its input, and a dictionary keyed by u8[:] stores views of strings rather than copies.
Nested data stays inline There is no single array type.
There is a family that differs only in what happens to the size, and every member is [metadata][elements...], inline and packed, never a pointer to an element block: Spelling What it is T[k] fixed size, known at compile time T[], T[varint] sized at construction, frozen after; a fixed or variable-width length T[..k], T[..]
capacity inline; grows and shrinks within it T[>..]