The Goose Programming Language

原始链接: https://aardappel.github.io/goose/

Hacker News new | past | comments | ask | show | jobs | submit login John Carmack on the Goose Language: multiple data stacks and relative references ( twitter.com/id_aa_carmack ) 13 points by bobbydigitales 23 minutes ago | hide | past | favorite | discuss help Consider applying for YC's Winter 2027 batch! Applications are open till November 2. Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact Search:
相关文章

原文

The Goose programming language

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.

Discord Chat Twitter Follow

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.

spec §4.3) says a constructed value is always built in its final home, propagated top-down through calls. A function returning a grow-only array by value writes its elements straight into the caller's variable, or into a field of the record being built inside another array, so out-parameters mostly do not appear.

summary explains the gains and losses; results.md has every measurement, and design.md explains what the suite tests.

What it costs you

  • You think about where data lives: who owns this, and how long does its scope last. Usually the answer is "the function that builds it", and that is free; when it is not, it is a reusable pool.
  • Recursive functions cannot keep growable data across a recursive call; scratch ends before the call, and what outlives it is passed in as a pool.
  • Arrays of variable-size elements iterate but do not index (short of an index of references kept beside them), a fixed-mode enum cannot be pointed into, and a variable-mode one cannot be overwritten. You choose per container.
  • No escaping closures, no function pointers, no dynamic dispatch beyond ADT tags, and whole-program compilation only.
  • A slot handed back to a reusable pool and still named reads whatever its next owner put there: a logic bug, never memory corruption.

Tutorial §18 is the full list.

Install a release

Prebuilt compilers for Windows (x64), Linux (x64) and macOS (Apple silicon and Intel) are on the releases page, with the graphics, audio, ui and physics packages compiled in. Unpack one anywhere and put its directory on PATH: the compiler finds the standard library, TinyCC's runtime and the packages beside itself, and keeps what it builds under ~/.goose (GOOSE_HOME). The archive also holds these docs, the samples and the VS Code extension.

Running a program in-process (goose file.goose) needs nothing else on Windows. On Linux it needs the C library's headers (libc6-dev, or glibc-devel); on macOS, the SDK of the Xcode command line tools (xcode-select --install). goose build also needs a C compiler: Visual Studio 2022 or later, or clang, on Windows; gcc or clang elsewhere. The Linux compiler needs glibc 2.35 or later (Ubuntu 22.04, Debian 12), the macOS one macOS 13.3 or later. macOS keeps a downloaded archive's files from running until xattr -dr com.apple.quarantine is run on the unpacked directory.

Build and run

You need CMake 3.27 or later, a C++20 compiler (MSVC, clang or gcc) and Python 3 for the test and sample runners. TinyCC, which the in-process backend is built from, is part of the tree. The graphics, audio, ui and physics modules are packages in repositories of their own (goose_gfx and goose_physics; docs/design/packages.md): -DGOOSE_BUILTIN_LIBS=all fetches them at the pinned tags (git, at configure time) and compiles them into the compiler, so their programs run with nothing else installed. Without it the compiler builds alone, and a program importing one of them has it built into ~/.goose the first time, which needs git, cmake and a C compiler.

third_party/SDL/docs/README-linux.md); without them the build leaves the gfx module out and says what to install.

The runner scripts prefer build/Release, then build/RelWithDebInfo, then build, then build/Debug. Pass --exe to select a specific compiler; use an explicit Debug executable when testing with compiler assertions enabled. The compiler is build/goose (build/Release/goose.exe with the Visual Studio generator), and it finds the standard library in the source tree it was built from. On Windows 10 version 1903 and later, its embedded manifest selects a UTF-8 process code page so compiler paths do not depend on the system locale. Earlier Windows versions retain their legacy code page. Generated native executables retain their own process settings.

Run a program straight from source, in-process (goose run says the same explicitly, and goose check only typechecks):

audio reference.

The test suite and the samples run on Windows, macOS and Linux:

VS Code extension gives syntax highlighting, the compiler's language server (goose lsp: errors as you type, hover, go to definition, completion, rename, symbols) and one-key runs through the JIT:

editors has the configuration for Neovim, Helix and Emacs.

Documentation

Status

Goose is new. What exists today is a whole-program compiler of about thirty thousand lines of C++, the specification, the standard library, the samples, and a test suite that CI runs on Windows, macOS and Linux with an extra sanitizer job. Deliberately out of scope for now: moves for resizable values, more than one resizable per struct, labeled break, namespace privacy, and more OS/library access. Open items are tracked in the specification's Appendix B.

License

Goose is licensed under the Apache License, Version 2.0.

Authors

Wouter van Oortmerssen: Language Design, Compiler Design, Coding standards. home page, twitter.

Claude Fable: Compiler implementation, Benchmarking, Sample & Doc writing.

History & Use of AI

This repo was produced almost entirely with AI, from a human language design. I had designed Goose several years ago, and had started to implement it, but running a game startup (which is built on another programming language of mine, Lobster) there was no time to finish it. Which was sad, because I knew Goose could do things other languages can't, and it should exist.

I had not expected AI to help with this until Fable came out. I thought it might be capable enough to do a good job. I made it essentially clone the style and structure of my other recent compiler (Lobster), which is why if you look at the code, it looks rather similar to that. My initial design had left lots of things unspecified, and lots of back and forth with Fable made me decide on all of those, and it is now a better language for it.

It is also an experiment in using AI to implement a new language, rather than reproduce an existing one.

You may wonder why I had it work in C++, if clearly I could have used any language, like Rust, or my own Lobster, or.. Goose itself (that may still happen). With Fable, it was my observation that it is almost equally capable in any language, and the strong guardrails of Rust or other languages are not as pressing as they once were. Since the compiler emits C, you likely already need to have a C/C++ compiler around, so sticking inside one ecosystem would seem to simplify deployment and adoption. I've also integrated libtcc and who knows what other C libraries in the future. And, like I said, to have the compiler code mimic my existing Lobster compiler seemed fun, at least I can read it like it is my own.

联系我们 contact @ memedata.com