
2026/09/19 22:55
Rust から来た場合の Zig の感じ方
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
7 年間 Rust を開発してきた著者が最近、
jsonpath-rust ライブラリを Zig に移植し、両言語の哲学間の明確なトレードオフを浮き彫りにしました。Zig は優れた速度と、メモリ割り当てを手動で制御したい人々が好む命令型スタイルを提供していますが、現在では Rust の成熟したエコシステムと比較すると未熟な印象を与えます。この移行には大きな構造的変更が必要であり、深い階層のフォルダ構造と統合された IDE(RustRover など)から、フラットなファイル構造と helix、alacritty、zellij などのツールの使用によるコマンドライン環境への移行を余儀なくされました。build.zig を通じたビルド管理は簡素化されていますが、テスト実行の摩擦により、冗長なインライン定義を使用するのではなく build.zig に明示的なテストを設定しました。重要な点は、Zig が Rust の自動メモリ安全性機能や関数型イディオム(例えばモノイド)を欠いていることです。その結果、開発者は各関数レベルで手動の割り当て管理(init/deinit)を行うなど、冗長なコードを書くことを強いられいます。Zig は現代的な設計によって C を凌ぐ可能性を秘めていますが、現在では生活の質的な機能と成熟したライブラリにおいて苦戦しており、例えば標準ライブラリの API は頻繁に変更され、現在の正規表現実装は堅牢なユニコード属性エスケープをサポートしていません。結局のところ、低レベルのパフォーマンスタスクには理想的ですが、自動的安全保証、広範なエコシステム、関数型パラダイムを優先するチームにとって、本番アプリケーションにおいては Rust の方が実用的である可能性があります。本文
Rust と Zig での JSONPath 実装比較:直感と代償の物語
はじめに
過去 7 年間、主にオープンソースプロジェクトで Rust 開発に従事し、言語とそのエコシステムへの堅実な感覚を養ってきました。私は Rust の関数型側面(純粋な関数、豊かなタイプなど)に惹かれていますが、同時に他の言語への好奇心も衰えません。特に Zig は、低級処理と軽量性を兼ね備えつつ、注目の C 語の後継候補として確かな地位を築いています。
キャリア早期に C 言語との経験があるため、この比較は非常に興味深かったのですが、当稿における Zig の体験は今回のプロジェクトから始まったものです。したがって、以下の観察結果は:
- 日常的に Zig を扱っている方々には一見幼稚で当然のものに見える可能性があります。
- 途中で下した判断が必ずしも最適ではなかったかもしれません。
それらは深い Zig 慣習というよりは、Rust から持ち越された習慣によって形作られたものです。それでも構いません。誰もがどこかから出発せねばならないからです。
公平な比較のため、すでに Rust で実装したものを Zig でも再実装することを決定しました。遊び心だけの試作ではなく、かつ膨大なプロジェクトでもない、実際にコミュニティが利用可能な「何か」を目指しました。その題材は RFC 9535 で規定された JSON クエリ言語「JSONPath(
jsonpath-rust)です。
IDE のサポート:CLI への回帰
まず驚いたのは IDE サポートのほぼ全然的欠如でした。Rust では IntelliJ Platform ベースの RustRover などを使用できますが、Zig では構文強調表示や基本的な自動補完しか提供されていませんでした。
- これが予想できたことかもしれませんが、私を「コマンドライン上で主に扱う」という基礎に戻しました。
- 最初は欠点のように思えたものが、体験の中で最も興味深い部分の一つになりました。
- 実は、純粋な CLI ツールに依存することはそれほど単純でなかったという事実を忘れていただけだったのです。
build.zig
による容易な課題解決
build.zig得られた最初の教訓は、
build.zig でこの課題が驚くほど容易に解決することです。
最終的に決定した構成:
# すべてのテストを実行 zig build test # 特定のテスト(例:"basic")のみを実行 zig build test -Dfilter="filter match function basic" # デバッグモードですべてのテストを実行 zig build test -Ddebug-query=true # コンプライアンススイートを実行 zig build compliance # ユニットテスト+コンプライアンスチェック zig build check
この前提を受け入れると、作業は非常に Refreshing になりました。Zig のおかげで大きな連鎖反応が始まり、完全に IDE に依存する環境から Helix(エディタ)+Alacritty(ターミナルエミュレータ)+Zellij(マルチパネート muxer)という構成へ移行できました。
平坦な構造:拡張性と直感のトレードオフ
Rust や多くの言語では、ファイルサイズとフォルダの階層深度のバランスを取るのに多大な時間を費やしてきました。しかし Zig はこれを積極的に奨励していない(C と同様です)。
- ネストのリスク: ファイルとフォルダをネストさせるとインポートの摩擦が生じます。「その分際ってどうするのか?」という問いが浮上します。
- 大文件の利点: 関連するものを一つの大文件にまとめ、セクションごとにスライシングすると、ナビゲーションや実質的な利点が大きいです。
基本的に Zig は平坦な構造を促します。モデルが必要なら隣に
model_<companion> を作成するだけで十分です。
- 大規模プロジェクトへの拡張性: 初期には拡張性がなさそうに見えますが、真の階層が必要になる閾値は予想よりも高いことが分かりました。
- 習慣と必要性: プロジェクトを整理するのは本当に必要か、単なる習慣かを再考させられます。一日目からフォルダに手を伸ばすことのできない規模感は、実際のそれより小さい可能性があります。
構造化の違い:並置比較
Rust (
):
多くの階層とファイルに分岐します(例:src/
parser/, query/ など)。
src/ ├── lib.rs ├── parser.rs ├── parser/ │ ├── errors.rs │ ├── macros.rs │ ├── model.rs │ ├── tests.rs │ └── grammar/ │ └── json_path_9535.pest ├── query.rs └── query/ ├── atom.rs ├── comparable.rs ├── comparison.rs ├── filter.rs ├── jp_query.rs ├── queryable.rs ├── segment.rs ├── selector.rs ├── state.rs ├── test.rs └── test_function.rs
Zig (
):
極めて平坦で簡潔です。src/
src/ ├── root.zig ├── parser.zig ├── model.zig ├── model_query.zig └── query.zig
テスト機能:冗長性の回避
RFC 9535 のコンプライアンススイートを一旦 aside に置き、言語そのものに焦点を当てます。
- Rust: インラインユニットテストがデフォルト。統合テストは独立したフォルダで例外扱い。
- Zig: 平坦な構造のため、個別のテストファイルを作るか、インラインさせるかの 2 つの選択肢に迫られます。どちらも clutter になりがちです。
私は後者の選択肢(テストを直接モデルファイル内にインライン)を選びました。
で明示的に構成する必要がありましたが、一度セットアップできれば良好に機能しました。build.zig- 総括: テストの作成・管理は Rust に比べて容易ですが、Zig の場合は言語固有の手動メモリアロケーション制度による摩擦が大きいです。
関数型パラダイムの欠如:設計思想の違い
Rust は命令型言語ですが、機能的概念(ゼロコストイテレータ、ADT、パターンマッチング、モノイド型等)を大いに採用しています。対照的に Zig はインプレイスな変異と命令的な世界へと追いやられます。
両者が近づく点:和型(Sum types)
Rust: 純粋で直接的なトレイト実装。 Zig: Duck-typed な実装(関数が存在するかを確認するだけ)。
再帰も双方で機能しますが、言語はすぐに機能スタイルから逸脱させるよう強制します。特に直接アロケーターと付き合うことが主な要因です。
具体例:Reduce vs. Fork / コビネーター vs. ループ
| 特徴 | Rust (コビネーター/イテレータ) | Zig (ループ/状態管理) |
|---|---|---|
| アプローチ | 新しい構造を構築するが、メモリコストが高い。 | 直接アロケーターを扱います。 で状態のコピーを作成。 |
| 例:Reduce | | その後 して破棄。 |
| 例:コビネーター | | ループでインデックスを管理。 でリストを直接変異。 |
- 結論: これは各言語の設計目標と対象ドメインを反映しており、妥当なトレードオフです。しかし主観的に、Zig コードは Rust の counterpart よりも読みづらいと感じました。
不変モノイド vs. 変異:核心の違い
Rust では単純なモノイド変換を行い、新しいデータ構造を返します。一方 Zig はインプレイスでのオーバーライトへ切り替わります。
- Rust:
からData::Ref
に移動させ、新しいData::Refs
を返す。Data - Zig: リストを直接変異させます (
など)。iteration.remove(i)
メモリ破損:構造体内への移動が失敗するとオランピングアロケーションが発生する
// 以下はバグ示例です pub fn appendBuggy(self: *Iter, v: *Value, path: []const u8) !void { const duped = try self.allocator.dupe(u8, path); // BUG: errdefer がいない -> アロケーション失敗時に duped を解放しない try self.cursors.append(self.allocator, .{ .json = v, .path = duped }); }
- 検出:
でテストすると、配列の拡張(第 2 のアロケーション)が失敗し、最初の文字列(第 1 のアロケーション)をオランピングします。FailingAllocator - 修正:
const duped = try self.allocator.dupe(u8, path); errdefer self.allocator.free(duped); // アロケートしたが連結失敗時の処理 try self.cursors.append(self.allocator, .{ .json = v, .path = duped });
Rust での比較:コンパイル不可能なバグ
Rust では
Vec::push(item) が item を移動させるため、成功か OOM で中止するかです。標準 API に「アロケートしたが連結されていない」値を手渡す失敗する push は存在しません。errdefer のギャップは最初から存在しないのです。
アロケーター:管理コストとテストツール
アロケーターはほぼすべての関数の引数にあり、各構造体もそれを保持します。
- 明示的であるが tedious:
/init
の慣習を慎重に守る必要があり、コールスタックが長い瞬間から規律が揺らぎ始めます。deinit - TestAllocator の活用:
が自動的にリークを検出してくれますが、失敗パスを実行するテストケースを書く必要があります。TestAllocator - 罠: コードが複雑になるにつれて、バグは絡み合い隠れてしまいます(例:
の順序付けミスによる二重解放)。defer
メモリリークの典型パターンと対策
- deinit の忘れ:
で検出。MemoryLeakDetected
を追加。defer iter.deinit() - エラーパスでの deinit スキップ: エラー発生時に所有者が適切に解放されず、リーク。
で解決。errdefer iter.deinit() - 二重呼び出し:
後にcache.append(result)
を呼び出すと、キャッシュ内のポインタを指すメモリーを解放してしまう。「値を所有するのは一層のみ」という原則を守り、移動(ownership transfer)のタイミングでresult.deinit()
を外します。defer
ライブラリとコア API:若いエコシステム
Zig のエコシステムはまだ非常に若いです。
- ライブラリの不足: 正規表現さえも完全に成熟しておらず、RFC 9535 のフィルター関数実装中にギャップとして浮上しました(例:
は Unicode プロパティエスケープをサポートしていない)。mvzr - API の変化: 標準ライブラリ自体がバージョンごとに API を変更することがあり、導入時点で予想されていたことですが記録として注記します。
全体的な印象
この言語は Rust と異なりますが(誰がこう思ったでしょう、そうですよね)、本当に良い印象を残しました。
- シンプルでモダン: 爆速です。
- C の真の後継者になるポテンシャル: 確信しています。
しかし、まだ若いことが示されており:
- 言語の形状自体の所々が未完成に見えます。
- 成熟するにつれて、より多くのクールなクオリティ・オブ・ライフ機能や構文糖を拾い上げる可能性が高いと考えます。
私はエコシステムに継続して貢献したいと考えており、価値あるプロジェクトが見つかるたびに貢献いたします。
リンク
jsonpath-rustzig-jsonpath- ZigRust
免責事項: この記事全体を通してのスタイリングとエラー処理は、AI の助けを借りて改善されました。