Chapter 1

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:

an array of three strings in C, Odin or Rust ptr len cap the array object ptr len cap word0 ptr len cap word1 ptr len cap word2 four separate heap blocks, each allocated and freed on its own the same array in Goose 3 5 word0 5 word1 5 word2 one block on a data stack: no pointers inside, freed by one store count
In most languages an array of strings is a tree of heap blocks. In Goose it is one contiguous block.

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:

#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:

  1. Getting Started covers running programs, scalars, control flow and functions.
  2. Memory Without a Heap is the heart of the book: data stacks, the array family, references and slices, strings, structs and optionals.
  3. Modelling Data covers enums, links between values, pools for data with irregular lifetimes, and error handling.
  4. Abstraction and the Library covers generics, recursion, the standard library and packages.
  5. Systems Programming covers threads, serialization, calling C, and performance.
  6. 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.