プログラミング
RustからZigへの書き換えはどのように進んでいるか
How Our Rust-to-Zig Rewrite Is Going (rtfeldman.com)
要約
Rocコンパイラチームは、1年半かけて30万行のRustコードをZigに書き換える作業を進めており、最近、元のコンパイラと同等の機能を達成しました。この書き換えにより、ホットコードローディングやパターンマッチングにおける型安全な文字列補間といった新機能が導入され、開発体験とパフォーマンスの向上が図られています。
全文翻訳
RustからZigへの書き換えはどのように進んでいるか
過去1年半にわたり、Rocのコンパイラを構築しているチームは、理由を後述しますが、30万行のRustコードをZigに書き換えてきました。最近、エキサイティングなマイルストーンを通過しました。それは、元のコンパイラと同等の機能が実装されたことです。Bunプロジェクトが最近、ZigからRustへの書き換え(ただし、これは私たちの書き換えの氷山の一角に過ぎませんが)に関する体験談を共有したため、RustからZigへの移行がどのように進んでいるかを振り返るのに良い時期だと考えられます。
機能パリティの達成
このマイルストーンに到達したことで、Brendan Hansknecht氏による魅力的な2024年のWASM-4ゲーム「Rocci Bird」(Luke DeVault氏のアートワーク付き)を新しいコンパイラを使用して更新することが可能になりました。これは良い例です。なぜなら、ゲーム全体が1000行未満のRocコードで構成されており、itch.ioまたはここからWebAssembly経由でプレイできるからです。
ゲームをクリックまたはタップし、スペースキー(またはタップ)を押して羽ばたかせます。モバイルでは右矢印キーがないため、ページをリフレッシュしてゲームを再開してください。
Rocci Birdの更新されたソースコードは、元のコードよりもいくらか簡潔になっており、roc build --opt=sizeコマンドは現在31KBのwasmバイナリを出力します。(元のコンパイラは、そのサイズの2倍以上のバイナリを生成していました。)Rocci Birdは決して大規模なコードベースではありませんが、それを実行可能にするためには、新しいコンパイラに多くの機能を実装する必要がありました。ついにそれが実現したとき、あのずっしりとした紫色のピクセルを見るのは、私に笑顔をもたらしました。
これはマイルストーンではありますが、正式なリリースではないことを明確にしておきます。(今年の後半にバージョン0.1.0のリリースを目指しています。)とはいえ、到達できたのは素晴らしいマイルストーンであり、これを実現するために協力してくれたすべての人々に心から感謝しています。
この言語とコンパイラをここまで到達させる上で特に役立った人々をいくつか紹介したいと思います。
新しいパーサーの共同開発者であるAnthony Bullard氏とSam Mohr氏
新しい型チェッカー(その他多くの貢献)を担当したJared Ramirez氏
新しいラムダセット解決システム、および元のコンパイラの多くの部分を担当したAyaz Hafiz氏
彼が元々作成したRoc Exercismコースの初級者向け演習108件を手動で更新したAurélien Geron氏
コンパイラの新しい「echo」プラットフォームをブラウザで動作させ、誰でもroc-lang.orgのホームページから2.5MBのWebAssemblyバイナリ経由で基本的なRocプログラムを記述・実行できるようにしたStephan氏
Rocの最も多作なプロダクションユーザーであるNiclas Åhdén氏。アップグレードプロセスに関する有益なバグレポートを辛抱強く提出し、実用的なフィードバックを提供してくれました。
JRI98氏。ファズエラーやその他のバグを体系的に再現・調査し、再現しなくなった問題をクローズするなど、多くの貢献をしてくれました。
新しいコンパイラを使用してユーザー空間パッケージのAPI設計を繰り返し改善したJasper Woudenberg氏。
元のコンパイラの基盤を構築し、新しいコンパイラが存在するきっかけとなったFolkert de Vries氏、Brendan Hansknecht氏、Brian Carroll氏、Josh Warner氏、Agus Zubiaga氏、Jelle Teeuwissen氏。
新しいコンパイラへの、議論の余地のない最大の貢献者を最後に残しました。Anton-4氏とLuke Boswell氏。彼らの貢献はあまりにも多く、すべてを把握しきれません。コンパイラ作業、ビルトイン、プラットフォーム、パッケージ、例、バグ修正、Roc Zulipでの初心者支援など…すべてを列挙すると、もう1つの投稿全体を占める可能性があります。彼らがどれだけ多くのものを構築したかを見るのは信じられないほどでした。皆さん、本当にありがとうございます。
このプロジェクトに貴重な時間をこれほど多く費やしてくれたことに光栄に思います。また、私たちの貢献者を支援することで、ここまで到達するのを助けてくれた、過去および現在のスポンサーであるrwx、Lambda Class、ohne-makler、martian、tweede golf、Vendr、NoRedInk、そして多くの寛大な個人スポンサーにも感謝します。
時間といえば:私たちの487日間の書き換えは、Bunが約50万行のZigをRustに書き換えた11日間よりも476日長くかかりました。この違いには、RustやZigとは無関係の多くの理由があります。それらの書き換えは直接的なポートでしたが、私たちは大幅に変更する予定だったため、書き換えることを決定しました。彼らが使用した手法は、私たちのケースでは機能しなかったでしょう。変更点のリストも、私たちの元のRustコードベースと新しいZigコードベースを比較しても、単純な比較にならないことを意味します。
それでも、新しいコンパイラがRocプログラマーにどのような新機能をもたらしたか、そしてRustとZigの経験をどのように比較できたかという両方の観点から、書き換えがどのように進んだかを振り返るのに良い地点に到達しました。それでは、始めましょう。
ホットコードローディング + クロスコンパイルバイナリ
Rocの新しいコンパイラは、開発中に自動的にホットコードローディングを行います。例えば、roc server.rocを実行してWebサーバーを起動し、実行中にそのコードの一部を変更できます。次にそのサーバーがリクエストを処理するとき、新しいコードを使用して自動的に処理されます。これはサーバーとシンプルな2Dゲームの両方で動作しています。
ホットローディングデモビデオをダウンロード。
ホットローディングはPythonのようなインタプリタ言語では標準的な動作ですが、Rocのような高性能コンパイル言語ではそれほど一般的ではありません。デプロイの準備ができたら、roc build server.rocでLLVM最適化されたスタンドアロンバイナリを取得でき、それをマシンにドロップして実行できます。Rocはクロスコンパイルも行います。Alpine Linuxで動作する静的バイナリをビルドすることは、roc build --target=x64muslという簡単なコマンドで可能であり、そのコマンドはMacまたは他のシステムで実行した場合でも、同じソースコードバイト(同じ入力ソースコードバイトに対して)と同じ出力バイトを生成します。これはすべてのコンパイラが保証するわけではありません。
文字列補間を伴うパターンマッチング
そのビデオのHTTPリクエスト処理ロジックは次のようになります。
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
}
これは、新しいコンパイラで導入されたいくつかの機能を使用しています。例えば、その"/users/${id}"という構文は、実行時にテンプレート文字列を解析して実装されているのではなく、新しい言語機能であるパターンマッチング内の文字列補間によって実装されています。これはコンパイル時に型安全であるだけでなく、このコードスニペット全体はヒープ割り当てをゼロで行います。ホットコードローディングを搭載した典型的な言語では、ここのコード行あたり平均1回の割り当てが発生すると予想されます…しかし、Rocはエルゴノミクス、型安全性、パフォーマンスにおいて高い目標を掲げています。
新しいroc-lang.orgのホームページでこの構文を試すことができます。少しスクロールダウンすると、ページ上にコンパイラのWebAssemblyビルドがあり、それを使って言語を試すことができます。ちなみに、HTTPリクエストルーティングをゼロ割り当てで実現するために、新しいコンパイラのコンパイル時純粋関数実行をどのように使用したかの技術的な詳細についての投稿に興味がある場合は、Roc Zulipで教えてください。
スクラッチからの書き換えの理由?
Rust、C、Zigとは異なり、Rocはシステム言語ではありません。自動メモリ管理(トレーシングコレクタの一時停止を回避するため、およびPerceus最適化とKokaのような機会的ミューテーションのため)を備えています。Rocは、クロージャキャプチャごとに1つのヒープ割り当てが必要な場合(ほとんどの非システム言語がそうであるように)、ヒープ割り当てがはるかに多くなりますが、Rocは多形的なデファンクショナライゼーションをラムダセットの特殊化を通じて実装した最初の非学術言語であるため、クロージャキャプチャはヒープを割り当てません。これはニッチな最適化に聞こえるかもしれませんが、Rocのような関数型言語では、デファンクショナライゼーションはインライニングに似ており、フォローアップ最適化の宝庫を解き放ちます。このシステムはRocの実行時パフォーマンスに非常に有益であることが証明されましたが、それを正しく実装するのは非常に困難であることも証明されました。元の実装では厄介なバグに苦しみ、Ayaz Hafiz氏がOCamlで新しいアーキテクチャをプロトタイプした後、ようやく新しいコンパイラでそれを正しく行うことができました。Ayaz氏のプロトタイプは、問題の根源が複数のコンパイラフェーズにわたるアーキテクチャにあることを示しており、それを修正するにはコンパイラの大部分を書き換える必要がありました。