Goose言語とは — ヒープなし・GCなし・ライフタイム注釈なしでメモリ安全を狙う新言語、実装はClaudeが担当

はじめに

Hacker News で 533 ポイントを集めたのが、新しいシステム言語 Goose です。設計したのは、ゲームエンジン Sauerbraten やスクリプト言語 Lobster で知られる Wouter van Oortmerssen(aardappel)。数年前に設計だけして止まっていたものを、実装・ベンチマーク・サンプル・ドキュメントのほぼすべてを Claude Fable に書かせて完成させた、と本人が明かしています。

Goose の特徴は、ヒープアロケータも GC も参照カウントもライフタイム注釈も unsafe も持たずにメモリ安全を目指している点です。この記事では、ヒープなしで何をどう確保しているのか、ベンチマークの数字をどう読むか、どんな用途に向かないのかを順に整理します。AI が言語処理系を丸ごと書いたという点も、HN での議論とあわせて取り上げます。

Goose とはどんな言語か

公式ページの一文目は次のとおりです。

A memory-safe systems language with no heap allocator, garbage collector, or lifetime annotations.

構文は C / Rust 風で、比べる相手としては C++ と Rust を挙げ、速度とメモリ使用量で勝負すると明言しています。主な仕様は次のとおりです。

  • メモリ: ヒープの代わりに、コンパイラが本数を決める「データスタック」を使う。スコープを抜けると解放される
  • 参照: T&<u32> のように、フィールド自身からの相対オフセットで表す。データの位置に依存しないので、to_bytes でそのままファイルに保存できる
  • 多態: 動的な多態は ADT(代数的データ型)だけ。継承・vtable・クロージャ・関数ポインタはない。ジェネリクスはすべて単相化される
  • エラー処理: return E from f と書くと、何段上のフレームでも、直近の f の呼び出しの戻り値として E を返せる。Result 型も ? もない
  • 並行処理: スレッドはメモリを共有せず、型付きキューで値を受け渡す。渡せるのはフラットな値だけで、受け渡しは memcpy 1 回
  • 出力: プログラム全体を 1 つの C ファイルにコンパイルする。同梱の TinyCC を使えばビルドなしでその場で実行もできる(スレッドを使うプログラムは通常の C コンパイラが必要)

現時点で存在するのは、約 3 万行の C++ で書かれたプログラム全体コンパイラ、仕様書、標準ライブラリ、サンプル 31 本、Windows / macOS / Linux で CI を回すテストスイートです。公式ページ自身が「Goose is new」と書いているとおり、まだ実験段階の言語です。

ヒープなしでどうやってメモリを確保するのか

データスタックと「1 スタックに伸びる値は 1 つ」

各データスタックは、仮想アドレス空間に大きな領域を予約しておき、ポインタを進めるだけで確保します(バンプアロケータ)。ルールは 1 つのスタックで同時に伸び縮みできる値は 1 つだけで、それは常に一番上にある、というものです。一番上にあるので、伸びても既存データを動かす必要がありません。

その結果、push が返す参照は配列が伸びても無効になりません。C++ の std::vector では再確保で参照が無効になるので、これは大きな違いです。

var items: Item[>..] = [];
let first .= items.push(Item { 1, 0.5 });
for i: i32 in 2..1000001 { items.push(Item { i, 0.0 }); }
first.weight = 99.5;   // 100 万回 push した後でも有効

配列の種類は「サイズがどう変わるか」で分かれます。固定長の T[k]、作った後は凍結される T[]、容量内で伸びる T[..k]、伸びるだけの T[>..]、伸び縮みする T[>..<] などです。

予約するのは仮想アドレスだけなので、物理メモリを消費するのは実際に触ったページだけです。HN では「スタックごとに予約した 2GB を超えたらどうなるのか」という質問も出ており、作者は「まだ起きていないし、サイズは設定できる」と答えています。

可変長データはインラインに詰める

可変長の部分も、ポインタで別の場所を指すのではなく、宣言順にその場に詰めて格納します。公式の例では、ある注文データが Goose では 29 バイト・確保 0 回、C++ では 160 バイト・確保 1 回、Rust では 153 バイト・確保 4 回になるとしています。

その代わり、可変長要素の配列は先頭から順にたどることはできても、添字で直接アクセスすることはできません。ここが Goose の一番大きなトレードオフです。

ライフタイムが入れ子にならない場合は reusable プール

木構造のように寿命がスコープの入れ子に収まらないデータには、内部にフリーリストを持つ reusable プールを使い、alloc_ref / free で明示的に管理します。ここで設計上の割り切りがはっきり出ます。解放済みのスロットを参照し続けると、次にそのスロットを使ったデータが読めるのです。公式はこれを「ロジックのバグではあるが、メモリ破壊には決してならない」と説明しています。型は常に正しいので未定義動作にはならない一方、use-after-free 相当の論理バグをコンパイラが止めてくれるわけではありません。

Rust の借用検査との違い

Rust はライフタイムと「可変参照は 1 つだけ」という排他ルールで安全を保証し、それをプログラマに注釈として書かせます。Goose では、すべての参照とスライスに静的な「ルート」(寿命を決める変数)があり、コンパイラがそれを推論して、持ち主より長生きしうる参照を拒否します。関数は渡されたルートごとに特殊化されます。注釈を書かなくてよい分、表現できる構造は最初から絞ってあります。リージョンベースのメモリ管理の系譜に入る発想で、作者はリポジトリに先行研究をまとめた文書も置いています。

ベンチマークをどう読むか

公式ページの要約は次のとおりです(16 本のベンチマーク、プロセス全体の実時間で計測)。

比較相手速度メモリ
慣用的な C++約 3.3 倍速い約 1.9 倍少ない
手で最適化した C++約 1.16 倍速い約 1.3 倍少ない
最良の安全な Rust約 1.12 倍速い約 1.2 倍少ない

読むときの注意点は 3 つあります。

  1. 作者自身のベンチマークで、独立した検証ではない。負けるケースも含めているとは書かれています。
  2. 要約の数字と、同じページの結果表(MSVC / clang 別)の数字が一致しない。たとえば慣用的な C++ 比は表では 3.51 倍 / 3.54 倍です。理由はページ上で説明されていません。
  3. HN では、作者が根拠に挙げた LangArena について「言語の速さではなく、AI がどれだけうまく実装したかを測っている」という批判が出ています。また、C++ の文字列で確保が多発するという例には「Small String Optimization で短い文字列は確保しない」という反論もありました。

「慣用的な C++」に大差をつけ、「手で最適化した C++」や Rust とは 1 割台の差、という分布は、勝因がアロケーションとポインタの削減にあることと整合します。速さそのものより、手で詰める最適化を言語の既定の挙動にしたところに価値があると見るのがよいでしょう。

AI が言語処理系を書いたことの意味

公式ページには次の一文があります。

This repo was produced almost entirely with AI, from a human language design.

人間が担当したのは言語の設計で、Claude Fable が担当したのはコンパイラ実装・ベンチマーク・サンプル・ドキュメントです。作者は、コンパイラの構造を自分の Lobster コンパイラに似せるよう指示し、Lobster のコードから抽出したスタイルガイドを与えたと説明しています。

HN では評価が割れました。批判側は、ドキュメントが AI 製だとどこまでが作者の意図なのか分からず、設計判断の理由を信用しにくいと指摘しました。擁護側は、コードもチュートリアルも実際に動かして確かめられると反論しています。

ここから読み取れるのは、言語設計の経験がある人にとって、処理系の実装に何年もかかるという壁が下がり始めているということです。一方で、仕様の意図を説明する責任は依然として人間側に残ります。

エンジニアへの影響

現時点で Goose を本番に使う理由はありません。ただ、次の 3 点は今の仕事にも持ち帰れます。

  • データ配置の考え方: 「ポインタで散らばったデータを、まとめて一か所に詰める」は C++ / Rust / Go でもアリーナやバンプアロケータで実践できます。Goose の 29 バイト対 160 バイトの例は、その効果を測る目安になります。
  • 向かない用途を見極める視点: 作者自身が、巨大で常に変化する共有グラフはメモリを共有しないスレッドモデルに合わないと認めています。AoS → SoA の自動変換もありません。カーネルや、アロケーションのランダム化を前提とするコードにも向かないという指摘がありました。ゲームのシミュレーション、パーサ、シリアライズが中心のツールなど、データの寿命がはっきりした処理が得意分野です。
  • AI に何を任せるか: 設計とスタイルガイドは人間、実装は AI という分担で、約 3 万行のコンパイラと 3 OS の CI まで到達しました。社内ツールや DSL を AI で作るとき、「設計文書とスタイルガイドを先に固める」という手順の参考になります。

まとめ

  • Goose は、ヒープ・GC・参照カウント・ライフタイム注釈なしでメモリ安全を目指すシステム言語
  • 仕組みは「データスタック+インライン格納+相対参照」。代わりに、可変長要素の添字アクセス不可やプール内の論理的 use-after-free といった制約がある
  • 速度とメモリの数字は作者自身の計測で、要約と表の数字にもずれがあるため、独立した検証を待つべき
  • 処理系の実装はほぼ Claude Fable によるもので、AI 製ドキュメントの信頼性も議論になった

AI によって処理系を作るコストが下がれば、Goose のような「棚上げされていた設計」が今後も表に出てくるはずです。言語の新しさだけでなく、設計の意図を誰が説明するのかにも注目していきたいところです。

ソース