2026年のZig vs Rust
(zackoverflow.dev)- 約3年前、バイトコードVMとガベージコレクタをZigとunsafe Rustで直接書いたときは、Zigの人間に優しいエルゴノミクスが優勢だったが、コーディングエージェントの時代に入り、その優位性は事実上ほとんど意味を失った
- Zigの主要機能がもたらす1.5〜5倍の開発者生産性向上は、Rustベースのコーディングエージェントがもたらす100倍の生産性向上に圧倒される
- Zigのallocatorインターフェース、任意ビット幅整数、packed struct、comptime などの中核機能は、いずれも人が直接コードを書くときに真価を発揮する機能
- Rustの型システムは、bounded polymorphismと不変条件の強制によって、エージェントのミスをコンパイル時に防ぐのにより効果的
- エージェントが生成するコード量が100倍に増えた状況では、Rustのメモリ安全性保証はZigに対して決定的な利点となる
核心的な変化
- Zigはunsafeコードのエルゴノミクスで大きな強みがあったが、人が直接コードを書く比重が減るにつれて、その強みの実用価値も小さくなった
- Zigの機能がもたらす1.5〜5倍レベルの人間の開発者生産性向上は、Rustでコーディングエージェントを使う100倍向上の陰に隠れる
- Zigの代表的な機能のかなりの部分は、人が手でコードを書くときの利便性を高めるが、コーディングエージェントにとってはその差がそれほど重要ではない
- 約3年前にZigとunsafe RustでバイトコードVMとガベージコレクタを書いた際には、unsafeコードを書く体験はZigのほうが良いと感じていた
- 2026年時点でもZigは良い言語だが、Rustのほうが好まれ、コーディングエージェントともより相性の良い言語へと変わった
Zigのアロケータインターフェース
- Zigのアロケータインターフェースは、特定のコードパスを最適化するために、arenaやstack fallbackのような特化アロケータを簡単に適用できるようにする
- ユーザー入力の1行を読む場合、入力長は理論上無制限なのでヒープアロケータが必要になるが、実際の入力はほとんどが短い検索語やパスであり、1KBよりはるかに小さい
std.heap.stackFallback(256, heap_allocator)は固定サイズのバッファをスタック上に置き、入力があふれたときだけヒープへ移るようにするため、一般的なケースではヒープ割り当てなしで処理できる- 以前のRustにはZigの
Allocatorインターフェースに相当する機能がなく、カスタムアロケータを使うVec<T>が必要なら、標準ライブラリ実装をコピーして修正しなければならなかった - Bumpaloのコレクションソースは、標準コレクションをforkしてbump allocatorにつないだ形だった
- Rust nightlyにはしばらく
Allocatortraitがあり、今では十分良さそうな水準になっている - Rustの
Allocatorはtraitベースなので静的ディスパッチを使い、Zigのアロケータはvtableベースという違いがある - Zigのようにデータ構造をアロケータ引数ベースで設計するコミュニティ全体の慣行はRustにはないが、AIがコードをコピーして変更しやすくなったことで、この制約の重要性は薄れた
任意ビット幅整数とpacked struct
- Zigの任意ビット幅整数と
packed structは、データ指向設計的なCPUキャッシュ最適化、tagged pointer、NaN boxing、bitflagsのような作業を容易にする - Obj-C APIをMetalとともにObjective-C runtime C APIで使う場合、
idはアラインされたヒープオブジェクトポインタではなく、tagged pointer である可能性がある - アラインメントを前提にしたコードへtagged
NSNumberを渡すとUBが発生しうるため、「ヒープポインタかtagged immediateか」を安価に検査する必要がある - 単純化したObjective-Cのtagged pointer配置では、下位1ビットが「ヒープポインタではない」ことを示し、次の3ビットがクラススロットを識別し、残り60ビットがpayloadになる
- Zigでは
enum(u3)とpacked structで、class: TaggedClass、payload: u60のようにビットレイアウトを型として直接表現できる - Zigでは
@bitCastで生のu64とObjcTaggedPointerを相互変換し、is_ns_numberでis_taggedと.ns_numberクラスを検査できる - Rustの対応コードでは、
ObjcTaggedPointer(u64)の中にTAG_MASK、CLASS_MASK、CLASS_SHIFT、PAYLOAD_SHIFTのような定数を置き、生成時にOR演算を行い、アクセス時にマスクを適用する - Rustではクラススロットは実際の型ではなく
u64定数であり、手で書く方式はZigよりエルゴノミクスが低い - Rustではbitfieldやbitflagsのようなcrateを使うほうがよいが、どちらもproc macroに依存しており、Zigの
packed structほど良いとは感じられない - コーディングエージェントがあれば、こうしたコードを手で書くのが面倒だという問題は大きく減る
comptimeの価値の変化
- Zigのcomptime は最も華やかな機能であり、一部の難解な依存型言語を除けば、Zigほど優れたコンパイル時計算を提供する言語はほとんどないと言える
- 実際の利用ではcomptimeをそれほど恋しく思わなくなり、使用量の約95%はパラメータ化された型のジェネリックデータ構造を作るために使われていた
fn ArrayList(comptime T: type) typeのように、型を受け取ってitems: []T、capacity: usize、allocator: Allocatorを持つ構造体型を返すパターンが代表的- Rustの型システムはZig風のcomptimeジェネリクスのかなりの部分を代替でき、より多くの不変条件を強制できる
- 残り約5%のケースではcomptimeがないことで不便で、信頼できる代替手段はcodegenしかない
- ゲーム開発中、ツールで生成されたhitbox geometryデータをハードコードしてデータ構造に入れたいとき、RustではClaudeにRustファイルを生成するスクリプトを書かせる必要がある
- それでも、コンパイル時計算が実際によく必要になるわけではない
Rust型システムの利点
- Rustの型システムはZigのcomptimeより価値の高い交換対象と評価され、とくにbounded polymorphism のためのtraits/typeclassesの領域で強い
- Zigで同程度のbounded polymorphismを実装しようとすると非常に難しい
- Rustの型システムはより多くの不変条件(invariant) を強制できるため、コーディングエージェントがよく犯すミスを防ぐ助けになる
- ゲームコードでは、euclid crateを使って、グラフィックスプログラミングでよくある問題である座標空間の混同を防いでいる
Point<Screen>やPoint<World>のように各座標空間に特化した型を作れば、ワールド座標とスクリーン座標を誤って混在させることがコンパイル段階で防がれるWorldPoint、WorldVector、ScreenPointを別型として置けば、同じ空間のpointとvectorの加算は許可されるTranslation2D::<f32, WorldSpace, ScreenSpace>によって、ワールド空間からスクリーン空間へ明示的に変換できる- 逆に、
let bad: ScreenPoint = player;のようにWorldPointをScreenPointに直接代入するコードは許可されない
メモリ問題をあまり扱わなくてよい効果
- コーディングエージェントが100倍多くのコード作成を可能にすると、Zigコードでメモリ問題をレビューしなければならない量も100倍に増える
- 形式的検証がなければ、バグを見つけるために調べるべき探索空間の表面積ははるかに大きくなる
- 現在のように生成されるコード量が大きい状況では、Rustのほうが魅力的だ
- Rustの従来のトレードオフは、borrow checkerに慣れていない場合に開発者生産性を損なうことだったが、コーディングエージェントがあればこの欠点の重要性は大きく下がる
- Rustで
unsafeを使う場合でも、miriのようなツールをコーディングエージェントに実行させて、UBが発生しないか、Rustのaliasingルールに違反していないかを確認できる
結論
- Zigは今でも恋しくなる言語であり、良い言語でもある
- 2026年の仕事のやり方では、Rustのほうが好まれ、コーディングエージェントとの相性もRustのほうが良い
1件のコメント
Lobste.rs の意見
以前のチームリードは、コピペコードは必ずしも悪ではないというかなり強い考えを持っていた。
DRY 原則のせいで本能的には間違っているか、少なくとも議論を呼ぶ話に聞こえたが、彼は非常に実務的な人で、主に大規模なテストコードベースにこの原則を適用していた。
気の利いた共通インターフェースを無理に作るより、単純だが大きく重複の多いコードベースのほうが保守しやすいことがある、という理屈だった。
最近 LLM を使う中で自分も同じ考えに戻ってきていて、今ではもっと重要なソフトウェア部分にも当てはめるようになっている。
コード生成は速く、LLM も 単純だが重複の多いコードベース のほうがうまく当てられる可能性が高そうだ。
重複を減らそうとしてテストに抽象化を入れすぎると、テストが理解しづらくなり、しかも微妙に間違う危険もある。
さらに悪いのは、テスト対象コードの抽象化を再利用すると、テストもそのコードと同じやり方で間違う可能性があることだ。
またアプリケーションコードと違って、テストは実質ただで「合成」できる。
テストハーネスを深刻に壊していない限り、テストを好きに追加・削除しても他のテストに影響せず、統合時の摩擦もないので、重複を避ける理由がひとつ減る。
テストではこれを DAMP と表現しているのを見たことがある。「Descriptive and Meaningful Phrases」の略で、再利用性より可読性を重視する原則だ。
この原則は似たコードを繰り返す形の重複を生むことはあるが、テストがより明白に正しく見えるようにしてくれる。
https://testing.googleblog.com/2019/12/…
Go コミュニティにも似た言い回しがある。「小さなコピーは小さな依存より良い」 https://go-proverbs.github.io/
ライブコーディングを共有してくれたことに感謝している。
記憶が正しければ、何かを始めるときに自分が作ろうとしているものに最も近いコードを見つけて丸ごとコピーし、そこから修正することがよくあった。
「2つが共有する抽象化は何かを座って長々考えないのか?」と思ったが、彼はただコピペして前に進み、しかも私よりずっと生産的だった。
Bun の Zig → Rust AI 書き換え のタイミングを考えると興味深い。 https://xcancel.com/jarredsumner/status/2053063524826620129#m
そのうち 75 件はデストラクタ、ムーブセマンティクス、borrow checker を持つ言語ならコンパイルすら通らなかったはずだという。
リリースされる PR の 3 件に 1 件が「エラーパスで解放し忘れた」という計算になる。
108 件のうち約 88 件は Zig 側にあり、C++ 側の約 14 件は主に参照サイクルや GC の並行性競合のような、どの言語でも残る残余カテゴリだという。
だから Zig→Rust の差は実在し、Zig のバグはまさにデストラクタと所有権で直せる種類であり、C++ 側はすでに底に近い。
より強いコンパイル時保証がない限り、これは今後もいたちごっこにしかならない。
提案は、最大のバグカテゴリを個別に修正し続けるのではなく、構造的に取り除こうということだ。
– bun/docs/rust-rewrite-plan.md at claude/phase-a-port · oven-sh/bun · GitHub
「残り 5% のケースでは comptime がないとつらく、同等の結果に確実に到達する唯一の方法はコード生成だ」という部分は、著者が何を意味しているのか明確ではない。
手続きマクロ について何も触れていないからだ。
きちんと作るのは少々面倒だが、できることは多い。
コード生成は不当に悪評を受けている面もあると思う。
build.rsスクリプトでかなり多くの厄介な問題をコード生成で解決してきたし、実際うまく動いている。もちろん、あとで後悔するかもしれないが。
記事の中心的な主張はだいたいこう見える。
Rust が良い言語なのは確かだが、それでもこれは少し大げさだ。
コーディングエージェントの宣伝のように見える。
同じ著者のリンク先記事にあった話だが、グラフィックスプログラミングで非常によくあるミスは 座標空間 を混同することであり、型システムはどの座標空間と変換が有効かを型で表現できるほど強力だという。
通貨、SI 単位とヤード・ポンド法の距離・重量、検証済み文字列とユーザー提供文字列、秘密値などにも同じことが言える。
また、うまく管理すれば状態型によって不可能または sound でない状態を防ぐこともできる。
ただ個人的に Rust で最も欲しい機能は データ競合の完全排除 だ。
管理言語にもデータ競合はある。
「じゃあ Go を使え」という話でも、Go ではあらゆるものがスレッド間で参照経由の可変になり、さらにスライス体操までついてくる。
以前は完全なおもちゃ言語だった、最も純粋で安全な側に属する JavaScript ですら、すべての
awaitが潜在的な競合になる。everything-is-an-EventEmitter パターンの邪悪さはひとまず置いておくとして。
だからその通り。GC さえあれば…… 🤫
少し核心を隠している感じがする。
コーディングエージェントは Python と JavaScript も非常にうまく扱う。
Rust より良いかどうかは主観的な話になるが、だからといって多くの仕事でそれらの言語を選ぶことにはならない。
問題は Zig の機能が頻繁に変わることにあるのか、それとも単に新しい言語だから AI 学習データ が混濁しているだけなのかが気になる。
Zig は Rust より書くのは難しいが、読むのは簡単だと感じる。
AI 時代には書くコードより読むコードのほうが多いので、私は Zig のほうを好むようになった。
今生成されているコード量を見ると、Rust のほうが魅力的だという話はまだ第一段階にすぎない。
コンピュータがさらに多くのコードを書くようになるほど、より 形式的な言語 が有利になるはずだ。
これは動的型付け論争の別の段階のように見える。
「動的型付けは人間には簡単なのに、なぜ機械のために同じことを三度も明示しなければならないのか?」という話だ。
型やライフタイム以外にも、機械が書き、消費するのをより容易にするものは何があるだろうか。
将来、コンピュータがコードを書くための言語では、人間が直接コーディングするのはどれほど難しくなるのか気になる。