Introduction
Every systems language has to answer one question before any other: when a program needs memory whose size it does not know in advance, where does that memory come from, and who gives it back?
C answers with malloc and free, and trusts you. Rust answers with
ownership: every heap block has exactly one owner, borrows are checked
against lifetimes, and the block is freed when its owner is dropped. Odin
answers with allocators. Every allocation goes through an explicit
allocator, the context carries a default one, and experienced Odin
programmers reach for arenas so that a whole phase of a program can be freed
at once.
Goose gives an answer that none of these languages give: there is no
heap. There is no malloc, no allocator interface, no garbage collector,
no reference counting and no destructors. Every value whose size is not
known at compile time lives on one of a small number of data stacks that
the compiler manages for you. Memory is taken from a data stack by bumping
a pointer, and it is given back, all at once, when the scope that owns it
ends. The compiler proves that no reference ever outlives what it points at,
and it does so without a single lifetime annotation in the source.
That sounds like a restriction, and in some ways it is. This book is about what you get in exchange, and how to write programs that work with the memory model rather than against it.
#Why learn Goose?1.1
The most visible reason is speed, which comes from data layout more than from anything clever in the compiler. Look at what an array of three short strings is in most languages:
In C, Odin or Rust the array is a small header pointing at a buffer of string headers, and each of those points at its own characters somewhere else on the heap. Building it costs four allocations, walking it touches four unrelated places in memory, and destroying it costs four frees.
In Goose the same array is a single run of bytes: a count, and then each
string's length followed by its characters. It was built by bumping a
pointer three times, it is walked from front to back, and it disappears
when its owner's scope ends, with no per-string work at all. There is also
nothing inside it that points elsewhere, which turns out to matter a great
deal: such a block can be copied with memcpy, written to a file as it is,
or handed to another thread as a single message.
The second reason is safety. Goose is memory-safe in the same sense as safe Rust: no use-after-free, no dangling references, no out-of-bounds access, no data races. But it reaches that point by a different route. Because nothing is ever freed except by a scope ending, the lifetime question the compiler has to answer is always the same simple one: does this reference outlive the variable that owns its target? There are no lifetime parameters to write, no borrow-exclusivity rules to satisfy, and two references to the same thing are perfectly fine.
The third reason is that the language is small and pleasant to write. It
has C-family syntax, type inference, generics without trait bounds, match
over algebraic data types, several return values, defer, and blocks that
are expressions. Much of it will feel like a cross between Odin and Rust,
and the parts that do not are the parts this book spends most of its time
on.
#What Goose is not1.2
It is worth being clear about the trade-offs before you invest time in them.
Goose compiles a whole program at once, to a single C file, and then builds that with a C compiler. There is no separate compilation and there are no Goose shared libraries. Functions can be passed to other functions, but only at compile time: there are no function pointers, no closures that escape, and no dynamic dispatch except through the tag of an enum. And you have to think about where data lives. Most of the time the answer is "in a local of the function that builds it" and there is nothing more to think about, but when data has lifetimes that do not nest neatly, you will need one of the tools from Part III.
The language is also young. This book was written against one specific build of the compiler, and some details will have changed by the time you read it. The ideas will not.
#A first taste1.3
Here is a complete Goose program. Do not worry about the details yet; every line of it will be explained in the coming chapters.
struct Goose { name: u8[..12], honks: i32 }
fn main() {
var flock: Goose[>..] = [];
let leader .= flock.push(Goose { name: "Ada", honks: 3 });
for i in 1..1000 {
flock.push(Goose { name: "", honks: 0 });
}
leader.honks += 1; // still refers to flock[0]
var names: u8[][>..] = [];
for i in 3 { names.push(str("goose #", i + 1)); }
print(flock.len, " geese, led by ", flock[0].name, " with ", flock[0].honks, " honks");
print(names, " in ", bytes_of(names).len, " bytes");
}
1000 geese, led by Ada with 4 honks ["goose #1", "goose #2", "goose #3"] in 36 bytes
A few things stand out even at a glance:
Goose[>..]is a grow-only array. It is the closest thing Goose has to astd::vector, an Odin[dynamic]Tor a RustVec<T>, but it never moves its elements. That is whyleader, a reference to the first element, is still valid after 999 more pushes..=binds a reference. A plain=would have copied the element.u8[..12]is a string that lives inside the struct, with room for 12 bytes. There is no pointer to a separately allocated string.namesholds three strings in 36 bytes: for each one, a 4-byte length and its 8 characters, packed one after the other.- There is no
free, nodefer delete(...)and nodrop. The two arrays disappear whenmainreturns.
#How to read this book1.4
The book assumes that you are comfortable with C and know at least one of Odin or Rust. It does not explain what a pointer, a struct or a stack frame is; it explains how Goose's versions of them differ from the ones you know. When a difference is mostly interesting to readers coming from one particular language, it is in a margin note labelled From C, From Odin or From Rust. Unlabelled notes are asides that anyone can skip on a first reading.
The book is in six parts:
- Getting Started covers running programs, scalars, control flow and functions.
- Memory Without a Heap is the heart of the book: data stacks, the array family, references and slices, strings, structs and optionals.
- Modelling Data covers enums, links between values, pools for data with irregular lifetimes, and error handling.
- Abstraction and the Library covers generics, recursion, the standard library and packages.
- Systems Programming covers threads, serialization, calling C, and performance.
- Projects puts it all together in two larger programs.
Most chapters end with a few exercises. Solutions are in Appendix C.
#About the examples1.5
Every example in this book was compiled and run with the prebuilt Goose
compiler of release v2026.3, from October 2026, apart from a few that
need a feature added since, which are marked where they appear. Where an
example prints something, the output shown below it is exactly what that
compiler printed. Where an example is supposed to fail to
compile, the error shown is the one the compiler gave. Short examples
usually show only the interesting lines; the surrounding fn main() { … }
is left out when it adds nothing.
If an example does not work for you, the most likely reason is that the
language has moved on. The official documentation, in the docs/ folder of
the repository, describes the current state of things and should be
trusted over this book.