Goose とは
Goose は、Wouter van Oortmerssen(aardappel)によって開発された新しいシステムプログラミング言語です。
C++ や Rust に似た構文を持ちながら、ヒープアロケータなし・ガベージコレクタなし・ライフタイム注釈なしでメモリ安全性を実現します。
公式ベンチマークでは、C++ や安全な Rust を上回る速度とメモリ効率を示しています。
核心コンセプト: 動的メモリを「データスタック」で管理。値はすべてインラインに配置され、スコープ終了時にポインタを戻すだけで一括解放されます。個別の free やデストラクタは存在しません。
| 項目 | 内容 |
|---|---|
| 作者 | Wouter van Oortmerssen(FlatBuffers、Lobster の作者) |
| ライセンス | Apache License 2.0 |
| コンパイル先 | C 言語(1 ファイルに出力、任意の C コンパイラでビルド可能) |
| バックエンド | バンドルされた TinyCC(ビルドステップなしでインタプリタ的に実行可能) |
| 公開日 | 2026 年 8 月末(GitHub 公開) |
主要な特徴
データスタックによるメモリ管理
Goose にはヒープがありません。代わりに、コンパイラが静的に割り当てる「データスタック」が複数存在します。 成長は単なるポインタの前進であり、スコープ終了時にポインタを戻すだけでメモリが解放されます。 100 万要素の構造体であっても、解放コストは 1 回のストアです。
for round in 3 {
var scratch: u8[>..] = []; // 成長専用配列
for i in 100000 { scratch.push((i % 256) as! u8); }
print("round ", round, ": ", scratch.len, " bytes");
} // スコープ終了で 1 ストアで解放
成長しても参照は無効化されない
通常の Vec や vector は再アロケーションで要素への参照が無効になりますが、Goose のデータスタックでは値が移動しないため、
配列を成長させても要素への参照は有効なままです。
struct Item { id: i32, weight: f32 }
var items: Item[>..] = [];
let first .= items.push(Item { 1, 0.5 }); // 要素 0 への参照を取得
for i: i32 in 2..1000001 { items.push(Item { i, 0.0 }); }
first.weight = 99.5; // 100 万回 push 後も有効
ライフタイム注釈なしのメモリ安全性
Goose はすべての参照とスライスに静的な「ルート」(対象の生存期間を規定する変数)を推論します。
参照が所有者より長生きする可能性がある場合、コンパイラはエラーを出します。
&'a も unsafe も不要です。
fn longest(a: u8[:], b: u8[:]) -> u8[:] {
if a.len >= b.len { a } else { b }
}
var outer: u8[>..] = [];
var w = outer[..];
{
var inner: u8[>..] = [];
w = longest(outer, inner); // エラー: inner をルートとする参照を
} // より長生きの変数 w に格納
ネストデータの完全インライン化
文字列、文字列の配列、可変長フィールドを持つ構造体の配列も、すべて 1 つの連続したバイト列として格納されます。 ポインタによる間接参照は発生しません。
| 言語 | サイズ | アロケーション数 |
|---|---|---|
| Goose | 29 バイト | 0 |
C++(std::string + std::vector) | 160 バイト | 1 |
Rust(String + Vec) | 153 バイト | 4 |
2 種類の Enum 表現
代数データ型(ADT)は Goose で唯一の動的ポリモーフィズムです。固定モードはタグ+最大ペイロード領域、
可変モード(Shape..)は各値がバリアントの実サイズだけを消費します。
enum Shape { Circle { r: f64 }, Rect { w: f64, h: f64 }, Dot }
var packed: Shape..[>..] = []; // 9, 17, 1 バイト(17 バイト固定ではない)
packed.push(Shape.Circle { r: 1.0 });
packed.push(Shape.Rect { w: 2.0, h: 3.0 });
packed.push(Shape.Dot);
for s in packed {
match s { Rect &r => { r.w += 1.0; }, _ => {} }
}
ポインタより狭いリンク(相対参照)
T&<u32> のような相対参照は、ターゲットへのオフセットを 1, 2, 4 バイトで持ちます。
これにより構造体はポジション非依存となり、ファイルにそのまま書き出せます。
読み込み時は検証パスで不正データを拒否できます。
共有メモリのないスレッド
ワーカーは独自のメモリ空間を持つ別プログラムとしてコンパイルされます。
スレッド間のデータ交換は、型付きキューを通じた memcpy のみです。
データ競合、ロック、アトミック、メモリオーダリングの概念が言語に存在しません。
多値リターンと長距離リターン
return err from load は、任意の階層上の load 関数からリターンします。
スタティックに検証され、アンワインディングや Result 型、? 演算子は不要です。
配列型ファミリー
Goose には単一の配列型ではなく、「サイズの変化方法」で区別された配列型ファミリーがあります。
| 型 | 意味 |
|---|---|
T[k] | 固定サイズ(コンパイル時に決定) |
T[], T[varint] | 構築時にサイズ決定、構築後は不変 |
T[..k], T[..] | インライン容量内で伸縮可能 |
T[>..] | 成長専用(最も一般的)。参照は成長後も有効 |
T[>..<] | 成長・縮小両方(スタック、キュー、ヒープ向け) |
T[:] | スライス:任意の配列からのビュー(コピーなし) |
文字列は u8 配列です。u8[>..] はビルダー、u8[] は完成文字列、
u8[:] はビューとして機能します。
ベンチマーク概要
16 のベンチマークの幾何平均(公式 README より)。Goose は速度とメモリの両方で優位性を示しています。
※ これらは公式ベンチマークスイートの結果です。すべてのユースケースで同様の結果が得られるとは限りません。 公式リポジトリには「負けたケース」も公開されています。
Goose 特性を活かしたサンプルコード
struct Item {
sku: u8[varint],
qty: varint,
cents: varint,
}
struct Order {
id: varint,
customer: u8[varint],
items: Item[varint],
}
fn main() {
var book: Order[>..] = [];
book.push(Order {
id: 1001,
customer: "alice",
items: [
Item { sku: "SKU-441", qty: 2, cents: 1999 },
Item { sku: "SKU-7", qty: 1, cents: 500 },
],
});
print(book);
}
struct Task { name: u8[>..], done: bool }
fn main() {
var tasks: Task[>..] = [];
let first .= tasks.push(Task {
name: "設計", done: false
});
for i in 2..101 {
tasks.push(Task {
name: str("task", i), done: false
});
}
first.done = true; // 100 回 push 後も有効
print(tasks.len); // 100
}
push が返す参照はデータスタック上で移動しないため、
配列成長後もダングリングしません。
enum Shape {
Circle { r: f64 },
Rect { w: f64, h: f64 },
Dot,
}
fn area(s: Shape.Circle) -> f64 { 3.14159 * s.r * s.r }
fn area(s: Shape.Rect) -> f64 { s.w * s.h }
fn area(s: Shape.Dot) -> f64 { 0.0 }
fn main() {
var shapes: Shape..[>..] = [];
shapes.push(Shape.Circle { r: 1.0 });
shapes.push(Shape.Rect { w: 2.0, h: 3.0 });
shapes.push(Shape.Dot);
for s in shapes {
print(area(s));
}
}
Shape.. は各バリアントを実サイズで保持。
Rust の enum と比較して最大 4 倍のメモリ削減と 2 倍の速度向上が期待できます。
struct Node {
key: i32,
left: Node&<u32>?,
right: Node&<u32>?,
}
fn main() {
var nodes: Node[>..] = [];
let root .= nodes.push(Node {
key: 10, left: null, right: null
});
let left .= nodes.push(Node {
key: 5, left: null, right: null
});
root.left = left; // u32 オフセットでリンク
print(nodes.len); // 2
print(root.left?.key);
}
T&<u32>? は 4 バイトの相対オフセット。
構造体全体をファイルに書き込んでもポジション非依存で読み戻せます。
fn load(path: u8[:]) -> u8[] {
let data = read_file(path);
guard data.len > 0 else {
return "empty file" from load;
}
parse(data);
return "ok";
}
fn parse(src: u8[:]) {
let comma = find(src, ",");
guard comma >= 0 else {
return "expected comma" from load;
}
// ...
}
return ... from load は load から直接リターンします。
中間関数のシグネチャを汚さず、Result や ? も不要です。
struct Job { n: i64 }
struct Result { n: i64, digits: i32 }
fn worker() {
loop {
let job = qget<Job>();
guard job.n >= 0 else { break; }
qput(Result {
n: job.n,
digits: count_digits(job.n),
});
}
}
fn main() {
spawn worker();
qput(Job { n: 12345 });
let res = qget<Result>();
print(res.digits); // 5
}
memcpy 通信します。
ロック、アトミック、データ競合の概念がありません。
制限事項とトレードオフ
- ヒープがない:
Box、Rc、循環構造体、一般的な共有グラフは直接表現できません。代わりに相対参照やプールを使用します。 - 可変長要素の配列はシーケンシャルアクセスのみ: インデックスアクセスはできません(別途インデックス配列が必要)。
- スコープに縛られたライフタイム: データの生存期間は基本的に「構築した関数」までです。それを超える永続化には
reusableプールが必要です。 - 言語の語彙が豊富:
guard、block、checkfree、reusable、from_bytesなど、学習すべき概念が多数あります。 - エコシステムが初期段階: 2026 年 10 月時点でまだ新しい言語であり、ライブラリやツールチェーンは今後の発展に依存します。
まとめ
Goose は、「ヒープなし・GC なし・ライフタイム注釈なし」という大胆な設計選択を通じて、 システムプログラミングにおけるメモリ管理とデータレイアウトの常識を書き換えようとする言語です。
データスタックによる一括管理、インライン化されたネスト構造、可変モード Enum、相対参照、 共有メモリなしの並行モデルなど、各機能が一貫したメモリ哲学の上に構築されています。 ゲームエンジン、シリアライゼーション、高頻度トレーディングなど、 キャッシュ効率とメモリレイテンシが重要な領域で特に強みを発揮する可能性があります。
一方で、ヒープの不在は表現力とのトレードオフであり、大規模コードベースでの長距離リターンの可読性や、 エコシステムの成熟度は今後の課題です。公式ドキュメントが充実しており、 チュートリアル・リファレンス・仕様書・31 のサンプルプログラムが揃っているため、 興味のある開発者はすぐに試すことができます。