Roc コンパイラの書き直し:Rust から Zig への進捗と得られた教訓

Roc コンパイラの書き直し:Rust から Zig への進捗と得られた教訓

機能同等性マイルストーン

新しい Zig ベースの Roc コンパイラは、元の Rust コンパイラと機能面で同等になりました。

  • このマイルストーンにより、Rocci Bird WASM‑4 ゲーム(Roc で 1 K 行未満)が roc build --opt=size31 KB の Wasm バイナリにコンパイルでき、Rust バージョンの半分のサイズになりました。
  • この成果は、今年後半に予定されている v0.1.0 へのプレリリースステップです。
  • 貢献者としては Anthony Bullard、Sam Mohr、Jared Ramirez、Ayaz Hafiz、Aurélien Geron、Stephan、Niclas Åhdén、JRI98、Jasper Woudenberg、そして主要貢献者 Anton‑4 と Luke Boswell が挙げられます。

"あの太くて紫色のピクセルを見たとき、やっと到達したときに笑顔がこぼれました!" – Joran Greef


ホットコードロード & クロスコンパイル

Roc は開発中のホットコードリロードと決定的なクロスコンパイルをサポートします。

  • roc server.roc を実行するとウェブサーバが起動し、ソースを編集すると再起動せずにライブサーバが更新されます。
  • roc build --target=x64musl は macOS、Linux、あるいは任意のプラットフォームで同一のバイナリを生成します。これはすべてのコンパイラが保証するわけではありません。

文字列補間を用いたパターンマッチング

文字列補間をパターンマッチの中で直接使用でき、ヒープ割り当てゼロのルーティングコードが生成できます。

match (verb, path) {
  ("GET", "/users/${id}/${page}") => match page {
    "" | "profile" => ok(id)
    "settings" => ok(with_default(user_agent, id))
    "posts/${post_id}" => ok("Post ID: ${post_id}")
    _ => not_found
  }
  ("GET", "/users/${id}") => ok(id)
  ("POST", "/posts/new") => created(with_default(...))
  _ => not_found
}
  • ルーティングロジックは ヒープ割り当てを行わず、コンパイル時に型安全が保証されます。
  • ライブデモは roc‑lang.org のホームページ上で、ブラウザ内 WebAssembly コンパイラを通じて利用可能です。

なぜスクラッチからの書き直しなのか?

Roc には単なる移植ではなく、根本的なアーキテクチャの刷新が必要でした。

  • 元のコンパイラの ラムダ集合特殊化(多相的デファンクショナリゼーション)はバグが多く、複数フェーズにわたって設計変更が必要でした。
  • 完全な書き直しにより、増分的な「テセウスの船」的リファクタリングを回避し、コンパイラプロジェクトがスクラッチから書き直すという一般的な慣行に沿いました。

なぜ Zig を選んだのか?

4 つの実務的な懸念が決定を導きました:

  1. ビルド時間 – Zig のインクリメンタルビルドはフィードバックループを劇的に高速化します。
  2. メモリ制御 – Zig のアロケータ中心のエコシステムは Roc のアリーナベース設計と合致しますが、Rust はグローバルアロケータを前提としています。
  3. エコシステムの適合性 – 必要な低レベル LLVM ビットコードツールは Zig にあり、Rust にはありませんでした。
  4. メモリ安全性支援 – Zig は ReleaseSafe など、より細かい unsafe コードチェックを提供し、unsafe セクションを多用するプロジェクトに適しています。

Borrow‑Checker がない生活

メモリ安全性の統計と実務的観察:

  • 2019 年の Microsoft の調査では、年間セキュリティパッチの 約 70 % がメモリ安全性バグに起因していることが示されました。
  • Zig の ReleaseSafe は実行時に use‑after‑free を検出し、ReleaseFast は本番向けにチェックを省略します。
  • Rust の borrow checker は多くの unsafe バグを排除しますが、Roc の 300 K 行コードでは 約 1 200 の unsafe ブロック(全体の約 0.4 %)が必要です。
  • 実際には、コンパイラがコードを誤生成した場合、両言語ともメモリ破壊バグを生む可能性があります。

書き直し後のメモリ安全性

バグレポート分析(Claude Opus 4.8 による分類):

バグ種別 Rust コンパイラ Zig コンパイラ
メモリ破壊バグ 21 10
非破壊バグ 2 575 421
合計 2 596 431
  • Rust の 21 件の破壊バグはすべて 誤コンパイル(生成プログラムがメモリを破壊)であり、コンパイラ自体のロジックバグではありませんでした。
  • Zig の 10 件の破壊バグのうち 8 件は誤コンパイル、2 件は実際のコンパイラバグ(エラーレポートコードでの use‑after‑free)で、Rust の borrow checker があれば防げたはずです。
  • 仮想的な代替シナリオ:
    • Zig ReleaseSafe:同じ 2 件はパニックで停止します。
    • Rust:コンパイル時に両方とも検出されます。
  • 結論:Roc のユースケースにおける実用的な安全性差は最小でした。

ビルド時間比較

コンパイラ 行数 (LoC) コールドビルド インクリメンタルリビルド
Rust 1.85(元) 354 K 32.4 s 10.0 s
Rust 1.97(現行) 354 K 25.4 s 3.4 s
Zig 0.16(機能同等) 320 K 39.6 s 8.6 s
Zig 0.17(現在) 464 K 32.1 s 0.035 s
  • Zig の現在のコードベースでのインクリメンタルリビルドは 35 ms で、Rust の 3.4 s の 100 倍以上の高速です(Rust の 18 ヶ月間の改善後でも)。
  • Zig 0.16 では -fincremental が壊れていましたが、0.17 で修正されています。
  • Rust のビルド時間短縮(2/3 減少)は印象的ですが、Zig のインクリメンタル速度には遠く及びません。

ゼロパースデシリアライズ(メモリ制御)

Roc はコンパイラデータ構造をディスクに直接キャッシュし、パースせずに再ロードします。

  • データ構造は 32 ビットインデックス構造体‑オブ‑配列 レイアウトを使用し、バイナリダンプが可能です。
  • 再ロードは本質的に memcpy 速度の操作で、ディスク I/O または OS キャッシュがボトルネックになります。
  • この手法は Zig が細粒度アロケータを管理できることに依存し、Rust のグローバルアロケータモデルでは安全性を保証するために多くの unsafe コードが必要になります。

エコシステム適合性

  • Zig は LLVM ビットコードエミッタやアロケータ中心のライブラリをすでに提供しており、Roc の要件に直接マッピングできます。
  • Rust のエコシステムはグローバルアロケータと Drop ベースのクリーンアップを前提としており、Roc のアリーナベース設計と衝突します。
  • Roc が再利用できた最も価値あるコンポーネントは Zig 自体のコンパイラ で、必要な LLVM ビットコードシリアライザをすでに実装しています。

Rust から失ったもの

  • 自動テストアロケータクリーンアップ – Rust のテストはリークを自動検出し、手動の defer/init が不要です。
  • パラメトリック & アドホック多相性 – Rust のジェネリックシステムは Zig の comptime ベースアプローチよりもリッチです。
  • プライベート構造体フィールド – Rust の可視性修飾子は誤ってフィールドにアクセスすることをコンパイル時に防ぎます。
  • 一貫した後方互換性 – Rust の安定リリースポリシーはアップグレードを楽にしますが、Zig は 1.0 前で破壊的変更が予想されます。

Zig で楽しかったこと

  • マクロがない – 言語表面がシンプルです。
  • 細かいデータレイアウト制御 – 非 2 の累乗整数型(u7u5)やパックド構造体を直接サポート。
  • 強力なビルドシステム – 単一コマンドで Alpine、WebAssembly、スタティックバイナリへのクロスコンパイルが可能。
  • エラーハンドリング – 明示的な try/catch スタイルが Roc の sum‑type エラーモデルと合致。
  • アロケータ中心 API – 回避策なしで Roc の設計にマッチします。

Roc の今後の予定は?

  • 今年後半に v0.1.0 リリース(最初の番号付きリリース)を目指します。
  • ドキュメント整備、標準ライブラリの磨き上げ、Roc Exercism 演習の拡充が進行中です。
  • 本プロジェクトは Roc Programming Language Foundation(501(c)(3) 非営利)と複数の企業スポンサーに支援されています。

コミュニティの反応(抜粋)

"Zig のインクリメンタルビルドは衝撃的な機能です。短期的にはなぜ切り替えるのかが分かりますが、Rust が追いつくことを期待できますか?"onlyrealcuzzo

"メモリ unsafe コードは機械語生成の大部分ではなく、ランタイムの方が重要だと思います。"steveklabnik

"Zig の ReleaseSafe は実際には use‑after‑free チェックを文書化していません。主張は大げさに感じます。"landr0id

"ビルド時間の数字は印象的ですが、比較対象が成熟した Rust コードベースと全く新しい Zig コードベースを混在させている点は注意が必要です。"dminik

"Zig は 1.0 前、Rust は 1.0 後です。この点だけでも多くの開発者の選択に影響します。"dev_l1x_be


結論

Roc の書き直しは、Zig が劇的に高速なインクリメンタルビルドと細粒度のメモリ制御を提供できることを示しつつ、元の Rust コンパイラと同等(場合によってはそれ以上)のメモリ安全性を実現しています。トレードオフとしては、エコシステムの成熟度が低く、破壊的変更が時折発生し、Rust が提供していた自動テストアロケータクリーンアップやリッチなジェネリック機構といった便利さが失われています。全体として、カスタムアロケータ、ゼロパースデシリアライズ、ホットコードリロードに強く依存するコンパイラに対して、Zig を採用した決定は妥当であることが進捗から裏付けられました。

Sources