Roc 編譯器重寫:從 Rust 到 Zig 的進度與教訓

Roc 編譯器重寫:從 Rust 到 Zig 的進度與教訓

功能等價里程碑

全新基於 Zig 的 Roc 編譯器現在在功能上與原始的 Rust 編譯器相匹配。

  • 這個里程碑讓 Rocci Bird WASM‑4 遊戲(少於 1 K 行 Roc)能以 roc build --opt=size 編譯成 31 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 需要一次根本性的架構大改,而不是簡單的移植。

  • 原始編譯器的 lambda‑set specialization(多態去函式化)存在錯誤,且需要在多個階段重新設計。
  • 完全重寫避免了「Theseus 船」式的漸進式重構,並與編譯器專案常見的從頭重寫做法保持一致。

為什麼選 Zig 而非 Rust?

四個實務考量驅動了這個決策:

  1. 建置時間 – Zig 的增量建置承諾大幅加快回饋迴路。
  2. 記憶體控制 – Zig 以分配器為中心的生態系統與 Roc 的 arena 設計相符,而 Rust 假設全域分配器。
  3. 生態系相關性 – 需要的低階 LLVM 位元碼工具在 Zig 中已有,但在 Rust 中尚未出現。
  4. 記憶體不安全協助 – Zig 提供更細緻的 unsafe 程式碼檢查(例如 ReleaseSafe),適合大量使用 unsafe 區段的專案。

沒有 Borrow‑Checking 的生活

記憶體安全統計與實務觀察:

  • 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
  • 所有 21 件 Rust 的毀損錯誤都是 編譯錯誤(產生的程式會毀損記憶體),而非編譯器本身的邏輯錯誤。
  • 在 10 件 Zig 的毀損錯誤中,8 為編譯錯誤;2 為真正的編譯器錯誤(錯誤報告程式碼的 use‑after‑free),這類錯誤若使用 Rust 的 borrow checker 本可避免。
  • 假設的替代方案:
    • Zig ReleaseSafe:相同的兩個錯誤會在執行時 panic。
    • Rust:會在編譯期捕捉到這兩個錯誤。
  • 結論:對於 Roc 的使用情境,實際的安全差異相當有限。

建置時間比較

編譯器 程式碼行數 冷建置 增量重建
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 失效而出現 bug;此問題已在即將到來的 0.17 版中修正。
  • Rust 的建置時間改善(減少至三分之二)固然令人印象深刻,但仍遠不及 Zig 的增量速度。

零解析度反序列化(記憶體控制)

Roc 直接將編譯器資料結構快取到磁碟,並在不解析的情況下重新載入。

  • 資料結構使用 32 位元索引結構化陣列(structure‑of‑arrays) 版型,允許直接二進位轉儲。
  • 重新載入本質上是 memcpy 速度的操作,唯一的瓶頸只會是磁碟 I/O 或 OS 快取。
  • 此技巧依賴 Zig 能細緻管理分配器;Rust 的全域分配器模型使得在不大量使用 unsafe 的情況下保證安全變得更困難。

生態系契合度

  • Zig 提供即用的 LLVM 位元碼產生器與以分配器為中心的函式庫,直接對應 Roc 的需求。
  • Rust 的生態系假設全域分配器與基於 Drop 的清理機制,與 Roc 的 arena 設計產生衝突。
  • 對 Roc 來說最有價值的可重用元件是 Zig 自身的編譯器,它已經實作了所需的 LLVM 位元碼序列化功能。

從 Rust 中失去的東西

  • 自動測試分配器清理 – Rust 測試會自動偵測記憶體洩漏,無需手動 defer/init 程式碼。
  • 參數化與即席多態 – Rust 的泛型系統比 Zig 的 comptime 方法更為豐富。
  • 私有結構欄位 – Rust 的可見性修飾子在編譯期提供防止意外存取欄位的安全性。
  • 一致的向後相容性 – Rust 的穩定發佈政策讓升級毫無痛感;Zig 尚未 1.0,破壞性變更是預期的。

喜愛 Zig 的地方

  • 沒有巨集 – 語言表面更簡潔。
  • 細緻的資料版型控制 – 直接支援非二的整數型別(u7u5)與緊湊結構。
  • 強大的建置系統 – 單指令即可跨編譯至 Alpine、WebAssembly 與靜態二進位檔。
  • 錯誤處理 – 明確的 try/catch 風格與 Roc 的 sum‑type 錯誤模型相契合。
  • 以分配器為中心的 API – 完全符合 Roc 的設計,無需額外的變通方案。

Roc 接下來的規劃

  • 目標在今年稍後推出 v0.1.0(第一個有編號的正式版)。
  • 持續工作包括文件撰寫、標準函式庫打磨,以及擴充 Roc Exercism 練習題。
  • 專案由 Roc Programming Language Foundation(501(c)(3) 非營利組織)以及多家企業贊助者支持。

社群回應(精選 HN 評論)

"Zig 的增量建置是個殺手級功能。短期內我能理解你們為何切換,但 Rust 能追上嗎?"onlyrealcuzzo

"我認為記憶體不安全的程式碼在產生機器碼時並不是主要問題;重點在執行階段。"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