GitHub Copilot Runtime 改寫 Rust:AI 輔助大型遷移怎麼驗證
大型語言或執行環境遷移最難的地方,通常不是把一段程式碼翻成另一種語言,而是證明翻譯後仍保留原本的行為、介面與營運邊界。GitHub 在 2026 年 9 月 16 日公開 Copilot Runtime 從 TypeScript/Node.js/V8 改寫到超過 80 萬行 production Rust 的案例,提供了一個值得拆解的驗證樣本。
這篇文章不是建議所有 TypeScript 專案都改寫 Rust。它把 GitHub 公開的案例資料轉成一套可套用的判斷順序:先定義終點,再保護獨立的測試 oracle,採用可回滾的增量切片,最後才討論 AI agent 能把多少程式碼寫出來。
先看案例的邊界,不要只記住行數
GitHub 文章指出,原本的 runtime 是 TypeScript、Node.js 和 V8,後來以 128 個合併到 main 的 pull requests 增量改寫;截至 8 月 21 日,production runtime 已是 Rust,並列出 832,378 行 production Rust、468,689 行 Rust unit tests,以及 174,675 行 E2E TypeScript tests。
這些數字很有參考價值,但不能直接推導出「AI agent 加 Rust 一定能在幾個月內完成任何大型遷移」。案例的目標包含低啟動成本、可嵌入程序、跨六種 SDK 語言的 FFI,以及共享的 runtime 架構;如果你的瓶頸是團隊熟悉度、第三方函式庫或資料庫相容性,答案可能完全不同。
| 公開案例中的條件 | 對其他團隊的可用問題 |
|---|---|
| runtime 需要被多種產品與 SDK 共用 | 你的遷移是否有清楚的架構收益,而不只是換語言? |
| 目標是可嵌入的 native library 和 C ABI | 你的邊界、部署與語言互通契約是否已寫下來? |
| 128 個 PR 分段合併並持續發版 | 能否把每個切片獨立測試、審查與回滾? |
| E2E 測試在每一步跑,測試本身不能被默默放寬 | 誰擁有不可變更的行為 oracle? |
第一步:把遷移終點寫成可驗證的契約
GitHub 文章提到,早期只說「把某個元件 port 到 Rust」時,agent 會把 I/O 或 orchestration 解讀成範圍外;當終點明確寫成 100% Rust native binary、沒有剩餘 TypeScript 執行環境時,執行結果才比較一致。
因此,遷移任務不要只寫來源語言與目標語言,至少要補上:
- 部署形態:in-process、subprocess、server 或混合模式。
- 公開介面:API、事件順序、錯誤格式、FFI 與 SDK 支援語言。
- 不變條件:哪些輸入輸出、權限、檔案操作、session 狀態與 timeout 必須保持一致。
- 清理終點:哪些 shim、舊 runtime、相容層和重複測試必須在何時移除。
- 非目標:這一階段明確不做哪些重構、效能優化或產品行為變更。
這份契約是後續 prompt、PR review 和驗證報告的共同參照。若終點不能轉成測試或檢查,先不要把工作交給 agent 批量展開。
第二步:選擇可持續的增量切片
GitHub 採用 in-place 的 atomic replacement:每個 PR 以新的 Rust 實作取代一小段 TypeScript,留下必要的薄 shim,讓 main 持續可發佈。這和 big-bang rewrite 的差別不只是 Git 分支策略,而是每個階段都把新程式放進真實的執行路徑。
適合先做的切片通常具有:
- 輸入輸出明確,且沒有大量共享可變狀態。
- 現有測試充分,能獨立證明行為沒有改變。
- 依賴圖較靠外圍,不會同時牽動所有 orchestration。
- 可以在單一 PR 內完成建置、測試、審查與回滾。
GitHub 案例也是先從純邏輯、無 I/O 或共享狀態的 primitive 開始,再逐步處理有狀態的 subsystem;這是可移植的工作順序,不是要求每個專案照相同模組名稱改寫。
第三步:保護測試 oracle,不要讓 agent 自己改答案
這個案例最重要的提醒不是 Rust compiler,而是 E2E 測試。GitHub 說明,許多缺少功能的 regression 都與 E2E 覆蓋不足有關;實作 agent 不能在沒有監督的情況下刪除測試、放寬 snapshot、提高相容性基線或套用逃生標籤,否則測試就不再是獨立的正確性 oracle。
可以把規則寫成遷移的硬閘門:
| Gate | 通過條件 |
|---|---|
| 行為 | 舊版與新版對同一組代表性案例給出相同的公開結果 |
| 測試 | 既有 E2E 與重要 integration test 通過,且測試變更有明確人工核准 |
| 邊界 | FFI、錯誤、取消、timeout、檔案與事件順序都有測試證據 |
| 效能 | 只記錄實測的 startup、memory、throughput 或 latency,不用編譯成功代替 |
| 清理 | 舊實作與 shim 的移除範圍可追蹤,不把未完成工作藏在相容層後面 |
這也是讓 AI coding agent 的完成條件可驗證的具體場景:done 不是 agent 回覆「已完成」,而是獨立的檢查結果足以支持合併決策。
第四步:把 AI agent 放在適合的位置
GitHub 的案例資料顯示,agent 大量用於閱讀、搜尋、舊新程式碼比對與局部修改;subagent 主要分散探索,協調中的主 agent 負責整合變更。這種分工比讓多個 agent 同時任意改同一個高耦合 subsystem 更容易維持一致性。
可以用下面的責任切分開始:
- agent:整理呼叫圖、提出 port plan、翻譯單一切片、執行編譯與測試、列出差異。
- 主協調者:決定切片邊界、處理跨模組衝突、維護遷移契約與安排 release。
- 人類 reviewer:判斷架構、API contract、風險、測試 oracle 是否被破壞,以及是否真的該合併。
這不代表人類必須逐行重寫或閱讀每個 agent 產生的檔案;重點是把高風險的判斷保留在能看懂系統目標的人手上,並讓低風險的機械比對交給自動化完成。
第五步:用發布節奏縮小回歸範圍
GitHub 文章記錄這次約 14.5 週的 porting window 內發出 135 個版本,包括 100 個 pre-release 與 35 個 stable,平均約每天 1.3 個版本。作者也指出,持續發佈讓 issue 更容易對應到最近變更,修正可以在下一個 pre-release 盡快驗證。
對一般團隊來說,不必複製這個精確頻率,但可以保留三個原則:
- 每個 release 只帶可說明的遷移切片,讓回歸範圍可定位。
- pre-release、內部環境與正式環境的通道要有明確升級條件。
- 將錯誤依功能缺失、狀態生命週期、邊界契約、效能與部署問題分類,避免只統計「測試紅了幾次」。
編譯器能抓到型別、名稱與 trait 等機械錯誤,卻不能證明事件順序、序列化格式、資源釋放或產品契約正確。因此,「能編譯」應該是最內層 gate,不是 release 的完成條件。
一份可直接套用的遷移檢查表
- 把終點、公開介面、不變條件與非目標寫成遷移契約。
- 先補齊最重要的 E2E 行為,再開始大量翻譯。
- 選一個低耦合、可獨立回滾的 pilot slice,驗證 build、packaging、測試與 review 流程。
- 禁止未經核准刪除或弱化 E2E、snapshot、compatibility baseline 與錯誤斷言。
- 讓 agent 負責探索與局部實作,保留主協調、架構和 merge decision。
- 以小批次 release 暴露變更,並記錄版本、切片、錯誤分類與回滾方式。
- 只有在行為穩定後,才另開工作討論 ownership、concurrency 或效能重設計。
結論:AI 降低的是遷移成本,不是驗證責任
GitHub 的公開案例證明,AI agent 可以讓一個原本需要大型團隊與長時間的 runtime rewrite 變得可行;它沒有證明「把整個 repository 交給 agent」就是安全流程。真正可移植的做法是:明確定義終點、切成可審查的 atomic replacement、保護獨立 oracle、持續發布,並讓人類保留對架構與 release 的最後判斷。
Q: Rust 編譯器很嚴格,通過 cargo check 就代表遷移成功嗎?
A: 不代表。編譯器能驗證語法、型別與部分資源規則,不能驗證舊版行為、事件順序、序列化契約、API 完整性或部署成本;仍需要獨立的 E2E、integration、效能與實際 rollout 證據。
Q: 遷移期間可以讓 agent 一起修改測試來修正失敗嗎?
A: 可以修改測試,但不能讓實作 agent 默默改寫正確性標準。若行為契約真的改變,應由負責人明確記錄原因、範圍和核准者;單純為了讓 port 通過而刪除或放寬測試,會失去 oracle。
Q: 一定要採用 Rust 才能複製這套方法嗎?
A: 不需要。Rust 是 GitHub 依據嵌入、效能、可靠性與跨語言 FFI 需求選出的目標;可移植的是契約、增量切片、獨立測試與發布閘門,不是特定語言。
參考資料: