- メモリ安全性のないZigでリークやクラッシュが続いたため、Bunは535,496行のコードを64個のAIエージェントでRustへ移植し、1〜2年かかる作業を11日に短縮した
- 成功の出発点は600行の
PORTING.mdで、ファイル単位の並列変換、2回の敵対的レビュー、コンパイルエラー修正、ローカルテスト、CI通過を順に進めた - 6,500件のコミットを作るのに、API価格ベースで16万5,000ドル、非キャッシュ入力59億・出力6億9,000万・キャッシュ入力読み取り720億トークンが使われた
- 手作業なら、コードベースをよく知るエンジニア3人が約1年間、製品改善、バグ・セキュリティ修正、新機能開発を止める必要があり、書き換え自体が難しかったと判断した
- 同じ方法を繰り返すには、コードベースを深く理解するエンジニア、結果を信頼できる強力なテストスイート、成功が不確実でもトークン費用を負担する意志が必要
BunがRustでの書き換えを選んだ理由
- BunはJavaScriptランタイムにとどまらず、複数の機能を提供する複雑なプロダクションプロジェクトである
- JavaScript・TypeScript・CSSの変換、バンドリング、ミニファイ
- テストランナーとnpm互換パッケージマネージャー
- モジュール解決、WebSocketクライアント、Node.js実装と複数のモジュール
- 月間ダウンロード数は2,200万件で、Claude CodeとOpenCodeが依存しており、Vercel・Railway・DigitalOceanが直接サポートしている
- Zigはメモリ安全な言語ではないため、最新のBunでもメモリリーク、メモリ問題によるクラッシュ、ヒープ範囲外への書き込みなどが発生し続けていた
- BunチームはZigコンパイラにパッチを当て、エンドツーエンドのメモリリークテストを導入したが、問題を取り除くことはできなかった
- ガベージコレクションされる値と手動管理される値の寿命を同時に扱う過程で、小さなリークや断続的なクラッシュが発生した
- すべてのメモリ割り当てについて、解放箇所と二重解放の有無、JavaScript例外処理、保守的スタックスキャナからのポインタ可視性などを検討する必要があった
- 安全なRustでは、use-after-freeとdouble-freeはコンパイルエラーになり、エラーパスで解放を忘れる問題は
Dropベースの自動クリーンアップで処理できる - Rust風のスマートポインタをBunのコードに独自導入する方法も検討したが、Rustより使い勝手が悪いうえ、同じ保証を提供できなかった
既存の書き換えプロジェクトが長期化する理由
- 書き換え中にも元のコードベースに機能が追加され続けるため、完了時期が繰り返し先送りされる傾向がある
- 9か月と見積もった作業は、9か月後にもさらに約6か月必要になることがある
- 15か月経っても、新機能に追いつくために数か月分の作業が残ることがある
- 運が良くても2か月間機能を凍結したうえで約18か月で終わり、当初9か月の見積もりが2年以上に伸びることがある
- コメントを除いたBunのZigコードは535,496行あり、小規模なエンジニアチームが別の言語へ移すには約1年が必要だと見積もった
- ユーザーから見える改善なしに1年を費やす選択肢は現実的ではないため、Fableで1週間以内にRust移植の可能性を検証できるか試すことにした
移植のための事前設計と検証
- 最初の段階でClaudeと約3時間、ZigのパターンをRustに近い形で対応させる方法を議論し、それを600行の
PORTING.mdにまとめた - 移植ガイドラインには、Bunの既存の実行構造を保つための具体的な制限を盛り込んだ
tokio、rayon、hyper、async-trait、futuresを使わないstd::fs、std::net、std::processのようにI/Oへアクセスするモジュールを禁止する- Bunが独自のイベントループとシステムコールを所有しているため、
async fnではなく既存のZigと同じくコールバックとステートマシンを使う - 借用チェッカーの衝突が発生したら、必要なスカラー値をローカル変数に保存して借用を終えてから、再度借用する
- 借用チェッカーを避けるための生ポインタの使用は禁止し、構造を変えた箇所には移植メモを残す
- 全1,448ファイルのうち3ファイルを先に書き換えた後、変更作業とは分離したセッションでClaudeが2回、敵対的にレビューした
64個のAIエージェントによる並列作業
- ファイルを互いに独立して処理できるように作業を分け、64個のAIエージェントを並列実行した
- 初期には、複数のエージェントが同じリポジトリ状態を触ったことで衝突が発生した
- あるエージェントが
git stashを実行した後、別のエージェントがgit stash popとgit reset HEAD --hardを実行した - エージェントごとに別のworktreeを割り当てると、Bunリポジトリのサイズのためディスク容量が不足し、変更内容を最終的に一緒にコンパイルしなければならない制約もあった
- あるエージェントが
- ワークフローを修正し、特定ファイルを即座にコミットするコマンド以外は
git stash、git resetなどのGitコマンドを禁止し、cargoや実行時間の長いコマンドも使えないようにした - 最終的に4個のworktreeへ作業を分割し、それぞれで16個のClaudeがファイルをコミットしてプッシュするよう構成した
- エージェントたちは2日間で535,496行のZigコードを移植し、各コミットは取り込まれる前に2回の敵対的レビューを受けた
コンパイルエラーとテストの修正
- 最初の変換は終わったがコードはコンパイルできず、Rustの最上位コンパイル単位であるcrateごとにClaudeがエラーを修正した
- 段階のタイトルには約1,600件のコンパイルエラーと書かれているが、引用では循環依存を解決する過程で約16,000件のエラーが明らかになったとされている
- エラー修正の過程も並列化した
- 各crateで
cargo checkを実行する - 出力をファイルごとにまとめ、エラーファイルを保存する
- そのcrateのコンパイルエラーをすべて修正する
- 2人の敵対的レビュアーが変更内容を確認する
- 1人の修正担当エージェントがレビュー結果を反映する
- 各crateで
- エージェントたちは深夜0時から午前11時30分まで、人の介入なしにコンパイルエラーを修正した
- その後約2日間をかけて、大規模なテストスイートをローカルでコンパイルエラーなしに実行できるようにし、失敗するテストを直してCIを通過させるためにさらに数日を投じた
- すべてのテスト通過と動作確認の後に変更をマージし、計画から完了まで合計11日かかった
- 約55万行のコード移植
- 6,500件のコミット
- 64個のエージェントを使用
コストと手作業との比較結果
- Fable API価格ベースで、書き換え全体の費用は16万5,000ドルだった
- 非キャッシュ入力トークン59億個
- 出力トークン6億9,000万個
- キャッシュ入力トークン読み取り720億個
- AnthropicはAPIトークンにマージンを乗せて販売しているため、実際の内部コストはこれより低い
- API費用は米国の中堅企業ソフトウェアエンジニアの年間基本給に近いが、同じ給与のエンジニア1人が11日以内に同じ成果を出すのは不可能だと評価した
- Fableは明確な報酬関数がある難しく集中した作業で特に優れているというMitchell Hashimotoの評価とも一致する
- 手作業なら、コードベースを完全に理解しているエンジニア3人が約1年間必要だったと見積もった
- その間、Node.js互換性の改善、バグやセキュリティ問題の修正、新機能の実装を進めるのは難しい
- 現実的な代替案は、書き換えずに既存のメモリバグを修正し続けることだった
他のプロジェクトに適用するための条件
- AIが1年かかる書き換えやマイグレーションを1週間程度に短縮するなら、以前は検討しにくかったプロジェクトも実行可能になる
- Bunの作業フローを再利用するには、3つの条件が必要である
- コードベースを非常によく理解し、作業への意欲が強いエンジニア
- テスト通過を実際の動作の根拠として信頼できるほど強力なテストスイート
- 成功するか事前に分からない状態でも、相当なトークン費用を投じる意志
- コードマイグレーションのような反復作業はLLMが比較的うまく処理するため、良いテストと問題を整理できるエンジニアがいれば成功可能性は高い
- すべてのプロジェクトに16万5,000ドルが必要なわけではない
- より単純なプロジェクトなら費用は下がる可能性がある
- 高レベルの計画には最も高価なモデルを使い、コーディングとレビューにはより安価なモデルを配置できる
- AIベースのマイグレーションは速くなっているが、Bunのようによくエンジニアリングされたプロジェクトでのみ、この速度を実現できる
まだコメントはありません。