CやRustに慣れた人ほど、この言語の主張には目が止まるはずだ。Gooseは、ヒープアロケータもガベージコレクタもライフタイム注釈も使わず、それでもメモリ安全なシステム言語だと名乗っている。しかも、掲載されているベンチマークではC++や安全なRustより速く、使うメモリも少ないという。単なる「変わった新言語」の紹介ではなく、メモリ管理そのものの前提をひっくり返そうとする試みとして読むと面白い。
元記事によると、Gooseは「メモリ安全なシステム言語」で、ヒープを持たず、GCもなく、ライフタイム注釈も不要だと説明している。コードの見た目はCやRustに近いが、動的な値はすべてコンパイラが管理する data stack 上に置かれ、スコープを抜けると自動的に解放される。つまり、一般的な言語でいう「新規確保して後で解放する」という発想をかなり外している。
特徴としてまず強調されているのは、メモリの配置と寿命がコンパイル時に決まることだ。データスタックはサイズを増やせるが、増えるときに中身を移動しないので、参照が壊れない。push した要素への参照を保持したまま、さらに100万回要素を追加しても、その参照は有効なままだと例で示している。コンパイラは「この参照はどこを根にしているか」を推論し、所有者より長生きしうる参照だけを拒否する。ライフタイム構文も unsafe もなく、借用の排他ルールもない、としているのがかなり攻めている。
配列や文字列の設計もかなり独特だ。Gooseでは配列がひとつではなく、固定長、構築時にサイズが決まる型、容量内で増減する型、増加専用の型、増減両対応の型に分かれている。どれもポインタ先の別領域ではなく、[metadata][elements...] のように中身をインラインで持つ。文字列も u8 配列として扱われ、構造体の中に可変長データをそのまま埋め込める。記事では、注文データの例で、Gooseでは29バイト・0 allocation なのに、C++の std::string + std::vector<Item> だと160バイト・1 allocation、Rustの String + Vec<Item> だと153バイト・4 allocations になると比較している。
Enum も2種類の持ち方がある。固定モードはタグと最大サイズのペイロードを持ち、可変モードは各値が自分の分だけのサイズを取る。可変モードの配列は順次走査向きで、代わりに中身への参照を持てる。リンクも面白く、通常のポインタより細い相対参照を使える。u32 オフセットの参照なら、データ構造をそのままファイルに書いて、そのまま読み戻せる。読み込み時には検証を行い、不正なデータを弾く。
さらに、関数呼び出しをまたいでエラーを返す仕組み、共有メモリを持たないスレッド、ジェネリクスや高階関数のゼロコスト実装、Cへの直接コンパイルなども特徴として並ぶ。記事は、GooseがCファイル1本にコンパイルされるので、Cコンパイラが動く環境ならどこでも動くとも述べている。TinyCCバックエンドを使えば、ビルドなしでその場で実行できるという説明もある。
まず驚くのは、Gooseが単なる「GCをなくした言語」ではないことだ。普通、GCを外すと手動管理か参照カウントに寄りがちだが、Gooseはそのどちらでもなく、そもそもメモリを「ヒープに置かない」方向へ極端に寄せている。これは設計としては筋が通っている。増えるたびに再配置しない、参照が安定する、解放はスコープ終端で一括、という流れは、分岐やランタイム処理を減らしやすい。ベンチマークで高速性を出しやすい土台は理解できる。
ただし、この設計がそのまま「何でも速い」に直結するかというと、そこは慎重に見たい。記事は16個以上のベンチマークで、Gooseが idiomatic C++ の3.3倍、手で最適化したC++の1.16倍、安全なRustの1.12倍の速度で、メモリもそれぞれ1.9倍、1.3倍、1.2倍少ないと主張している。数字だけ見るとかなり強いが、ベンチマークはどうしても相性がある。メモリレイアウトが効く処理では強く出る一方、汎用性や運用性まで含めた総合力は別問題だと思う。記事自体も「Gooseが負けるケースもある」と触れているので、そこは誠実だ。
それでも、メモリ安全性と性能の両立を「言語機能」ではなく「データ配置の原理」から解き直している点は新しい。Rustは安全性を借用規則で守るが、Gooseはコンパイラが所有関係を根にたどっていく。安全性の代償として書き手に負担を戻すのではなく、言語側がレイアウトと寿命を先回りして決める。思想としてはかなり徹底している。ここに惹かれる人は、単に速いからではなく、低レベル処理の複雑さを別の方法で片づけたいからだろう。
個人的にいちばん面白かったのは、push しても参照が生き続ける点だ。Vec や vector では再確保が起きると参照やポインタが死ぬので、reserve を気にしたり、インデックスに切り替えたりする。Gooseはそこを最初から言語仕様に埋め込んでいる。これはアルゴリズム実装、特に木構造やグラフ、インデックス付きのバッファ操作で効きそうだ。記事中の二分探索木の例も、ノードを12バイトに収め、1回の push と1回の代入で挿入できるとしていて、かなり気持ちよく設計されている。
一方で、参照が安定することと、あらゆるデータ構造が扱いやすいことは同じではない。可変サイズの要素を含む配列は順次アクセス向きで、ランダムアクセスに制限が出ると記事ははっきり書いている。つまり、速さの代償として「できることの形」をかなり選ぶ。一般的なアプリケーションでは、検索、更新、部分的な再配置、差し替えが頻繁に起きる。そういう場面で、このインライン前提のレイアウトがどこまで自然に使えるかは、実装者の腕よりも言語の適用範囲に左右されるはずだ。
だからGooseは、万能言語というより、データの形がはっきりしている領域で光る言語に見える。ファイルフォーマット、ストレージエンジン、パーサ、メッセージ処理、ゲームのデータ構造など、メモリ配置を詰める価値が高い場所にはかなり刺さるかもしれない。逆に、業務アプリのように変化が多く、複雑な依存関係を持つ領域では、この「気持ちいい」設計がそのまま利点になるとは限らないと思う。
Gooseの説明を読んで感じるのは、Rustとの違いが単に文法の違いではなく、面倒の受け持ち先の違いだということだ。Rustは安全性のために、借用やライフタイムの制約を開発者が意識する場面がある。Gooseはそこを消し、かわりにコンパイラがデータの根を推論する。unsafe がないのは潔いが、その分だけ、言語が許すデータ配置や参照の張り方はもっと厳しくなる。自由度と引き換えに、最初から安全な形しか通らない作りだ。
この方向性は、CやRustの置き換えを狙う新言語の中でもかなり野心的だと思う。しかも、記事は単に「安全です」と言うだけでなく、保存・読み込みがそのままできる、スレッド間の共有メモリがない、Cにコンパイルできる、といった実装上の強みを並べている。これは机上の理論ではなく、システムを構成する部品をかなり具体的に再設計している印象を受ける。
ただ、言語の成功は機能の足し算では決まらない。ライブラリ、デバッガ、IDE 支援、既存資産との接続、学習コスト。そこまで含めると、Gooseはまだ「魅力的な原型」の段階に見える。とはいえ、ヒープを前提にしない設計がここまで筋道立って見えるのは珍しい。性能を追いながら安全性も捨てたくない人にとって、この言語はかなり刺激的な提案ではないか。