世界を動かす技術を、日本語で。

RustからZigに移行したときの感覚

2026年9月19日原文(besok.github.io)

概要

  • Rust 開発者が Zig でJSONPathライブラリを再実装した経験の比較
  • IDEサポートプロジェクト構造テスト方法 の違いに注目
  • 関数型プログラミング スタイルとZigの命令型アプローチの比較
  • メモリアロケータ 管理の違いとその実践的影響
  • 各言語の設計思想と開発体験の主観的な評価

Rust開発者によるZig体験記

  • Rust で7年間、主にオープンソースプロジェクトに従事した経験
  • 関数型志向 を好み、クリーンな関数や表現力豊かな型を重視
  • Zig はCの後継候補として注目、低レベルかつ軽量な特徴
  • 比較のため、既存のRust製JSONPath(RFC 9535)ライブラリを Zig で再実装
  • Zigは初挑戦であり、Rustの習慣が一部影響した意思決定

IDEサポートの違い

  • RustRover やJetBrainsのIDEと比較し、 Zig のIDEサポートはほぼ皆無
  • 基本的なシンタックスハイライトと補完のみ
  • コマンドライン中心の開発体験へ回帰
  • build.zig によるビルド・テスト管理が意外とシンプル
  • 結果的に helix + alacritty + zellij 環境へ移行

プロジェクト構造の設計

  • Rust ではファイル・フォルダ分割を早期に導入する傾向
    • 複数階層のフォルダと細分化されたファイル構成
  • Zig はフラット構造を推奨しがち
    • ファイル数・階層を極力減らし、関連要素を一箇所に集約
  • 大規模プロジェクトでは階層化が必要だが、Zigではその閾値が高い
  • 他言語の設計にも影響し、「本当に階層が必要か」を再考する契機

テストの運用方法

  • Rust
    • インラインユニットテストが主流、利便性が高い
    • 統合テストは例外的存在
  • Zig
    • 同じファイル内でテスト記述は可能だが、冗長になりやすい
    • フラット構造ゆえ、テストの配置に悩む
    • build.zig で明示的な設定が必要
  • 総じて Rust の方がテスト管理が容易に感じられる

関数型パラダイムと命令型の違い

  • Rust は関数型の要素が強い
    • ゼロコストイテレータ、遅延評価、ADTs、パターンマッチ、モナド型、トレイト、クロージャ等
    • 不変変換やイテレータ合成、宣言的マクロ
  • Zig は命令型・ミュータブルが基本
    • アロケータを直接扱い、in-placeミューテーションが主流
    • サムタイプ(列挙型)は両言語で近いが、関数型的な合成表現は難しい
  • Rust ではモナディックな変換が自然、 Zig では直接的なミューテーション処理が多用される

メモリアロケータ管理

  • Zig はほぼ全ての関数・構造体でアロケータを明示的に扱う
    • init/deinit の厳格な運用が必要
    • 手動でリソース管理するため、スコープが深くなるとバグが発生しやすい
  • Rust はスコープ終了時に Drop が自動実行され、リソースリークが起きにくい
    • ただし、循環参照や明示的なリーク(Box::leak等)は例外
  • ZigTestAllocator でリーク検出が可能だが、全てのパスを網羅するテストが必要

主観的な総括

  • Zig はCライクな低レベル制御と明示的なリソース管理が特徴
  • Rust は関数型志向や自動リソース管理による生産性・安全性が魅力
  • 設計思想や開発体験が大きく異なり、プロジェクトや個人の好みに応じて選択が分かれる

Hackerたちの意見

最初に驚いたのは、正直言って誰がこれが印象に残る部分だと思っただろうか、IDEサポートのほぼ完全な欠如だった。これは完全に予想してたことなんだけどね。

2020年代を振り返ると、アロケーターへの執着に驚かされると思う。Handmadeの「Cの後継」言語は、ZigだけじゃなくてOdinやC3、Jaiもこの執着を持ってるみたい。おもちゃの問題に対しては、巧妙なアロケーターのトリックを使って大きなパフォーマンス向上が得られることもある。例えば、JaiとOdinは、定期的に「フレームごと」のアリーナを捨てられるコードを書くことを本当に望んでいるみたいで、アリーナ内のアロケーションを追跡するためのコストを払わなくて済むから、同時に全部捨てられるんだよね。でも、実際のソフトウェアはそんなに単純じゃない。こういう機能が無価値ってわけじゃなくて、経験豊富な開発者がツールキットに欲しい千のツールの一つってことだと思う。だから、あまり目立つ存在ではないかな。

AAAゲームはアロケーターを広く使ってるし、他のソフトウェアよりも複雑で高いパフォーマンスが求められるから、君が言ってるおもちゃの問題が何かよくわからない。アロケーターは2020年代よりずっと前から広く使われてたけど、最近また興味が復活してるのは同意する。ただ、データ駆動設計に焦点を当てるというより広いトレンドの一部だと思う。メインメモリにアクセスするのがプログラムの中で一番遅いことの一つだから、これは理にかなってるよね。

実際に持続的な大規模プログラムが重要なんだ。カーネルやデータベースは、アロケーションに多くのポリシーが絡んでいるから、明示的なアロケーターを使うことが多い。これは数十年前からの話で、2020年代の特徴だけじゃないよ。システムプログラマーとしては、これが常に必要だし、アロケーターの状態を完全に隠して暗黙的にするのは短絡的だと思う。でも、アロケーターを渡すのはちょっと面倒だよね。私の本当の不満は、もし'malloc'がグローバルでコンパイラのプリミティブなら、アロケーションを統合したり、適切な場合にコンパイラ管理のライフタイムを持つことができるってこと。明示的なアロケーターは重要な手段で、マイナス面があってもそれを可能にする方法を見つけられないとは思えないな。

うん、これも気づいてた。みんなパフォーマンスにこだわるプログラミングコミュニティの異端な一角から来てるんだよね。現代のソフトウェアが遅いことを考えると、これは悪いことじゃないけど!でもこのグループはパフォーマンスがメモリの問題だと思ってるみたいで、それは一部のソフトウェアには当てはまるけど、全てには当てはまらないと思う。これらの言語は、メモリ割り当てに実際のコストがかかるゲームのようなリアルタイムアプリケーションには本当に役立つと思うけど、いわゆる「ポインタジャングル」がMicrosoft Teamsが起動するのにトリリオンのCPU命令を必要とする理由ではないと思う。

ちょっと言い方を変えたいな。「アロケーター効果システムへの執着」みたいな感じで。アロケーターのトリックが現代のソフトウェアに役立たないわけじゃないけど、私が関わった大きなアプリケーションは、少なくとも時々はアリーナ割り当てに頼ってるからね。でも、コンテナタイプをアロケーターに対して一般化するのは、やる価値がないように思える。例えば、STLタイプのデフォルトアロケーター以外を使ってる人を見たことがないよ。

でも、多くの現実のソフトウェアはそんなにシンプルじゃないよ。Zigは一方に偏ることも強制しないし、むしろ好まないんだ。もし、どんな順番でも回収できる控えめなヒープ割り当てが欲しいなら、そのためのアロケーターがある。リクエストやビデオフレームの終わりに捨てるためのアリーナが欲しいなら、そのためのアロケーターがある。固定のバックバッファを使いたいなら、そのためのアロケーターもある。要するに、標準ライブラリは一方を前提にしてないから、「現実のソフトウェアはそんなにシンプルじゃない」っていう問題に対しては良いと思う。

メモリの割り当ては必ず起こるよ。コンパイラやランタイムに任せることもできるし、コードで制御することもできる。自動的な解決策は大体良いし、最初に試すには適してることが多い。ただ、パフォーマンスが優先なら、選択肢が欲しいよね。ちなみに、「フレームごとのアリーナ」は、重要な割り当てを行う作業の繰り返し間隔の一般的なパターンの一部なんだ。これはあらゆる種類のソフトウェアでよく見られる…リクエストを処理するサーバー(ウェブサーバーやデータベースサーバーみたいな)や、一般的なコマンドラインツールでね。

例えば、JaiとOdinは、定期的に「フレームごとの」アリーナを捨てるコードを書いてほしいみたいだね。この考え方は、フレームごとのセマンティクスを超えて一般化できるよ。HTTPリクエストごと、pubsubメッセージごとに考えてみて。各々に対して新しい仮想アリーナを作成して、それをcontext.temp_allocatorとして設定し、リクエストやメッセージ全体で使うことができる。その後は捨てちゃえばいいんだ。

これの一部は、Rustが最初は標準APIレベルでアロケーターをサポートしていなかったことから来ているんじゃないかな。自分でアロケーターを手動で作成したり、コンテナを作成・修正したりできるけど、GlobalAllocが登場するまでは、複数のものが一つのものを使うためにArcを通すようなことが必要だったこともあった。グローバルのものを使うのが最適とは限らない(割り当てサイズ、再利用、ライフタイムに基づいて特化するのが良いことが多い)けど、私は過去16年間、グラフィックス、レンダリング、シミュレーションのHPCをやってきて、C++でかなり使ってきたから、「おもちゃの問題」にしか役立たないって言うのは、君がやっていることの中で必要性を見つけられていないだけかもしれないね。

カスタムアロケーターの有用性は、問題の複雑さとはあまり関係ない気がする。スクラッチアリーナの例を考えてみて。動的な割り当てがクリティカルパス上で始まり終わるとき、パフォーマンス(&シンプルさ)がただ得られるだけなんだ。おそらく、JaiやOdinがこの状況に注目しているのは、ビデオゲームのフレームごとの処理に関わるからだろうけど、一般的には非自明な問題でこれがもっと頻繁に出てくると思うよ。

アリーナアロケーションの使い方を学ぶことをおすすめするよ。こんな感じのパターンがある:fn run_query(alloc) { arena = init_arena(alloc); defer arena.deinit(); }

Hacker Newsで議論の続きを見る