從 Go 遷移到 Rust:效能提升 vs. 維運權衡

Go 與 Rust 之間的爭論通常被框架化為簡單性與強大功能之間的二元選擇。對於許多工程團隊而言,從 Go 遷移到 Rust 的決定是由對更嚴格的記憶體安全性、消除垃圾回收 (GC) 停頓以及更高的原始效能之渴望所驅動的。然而,正如許多從業者所指出的,這種轉型不僅僅是語法的改變——它是團隊管理狀態、並行處理與軟體供應鏈方式的根本轉變。

核心驅動力:安全性與確定性

遷移到 Rust 的主要論點之一是從「在審查中發現」轉向「在編譯前發現」。在 Go 中,安全性通常取決於紀律和徹底的程式碼審查。Rust 則將這些保證編碼進型別系統中。

記憶體與 Null 安全性

Go 對指標的依賴以及 nil 解引用(dereference)的可能性是生產環境事故的常見來源。Rust 的 OptionResult 型別強制開發者明確處理值缺失或錯誤發生。雖然批評者指出 unwrap() 可以繞過這些檢查,但借用檢查器(borrow checker)的慣用用法可以在編譯時消除整類型的資料競爭(data races)。

確定性與效能

對於專門的系統,Rust 提供了一種 Go 的受管執行環境(managed runtime)無法比擬的確定性。正如一位開發者所言,將一個資料鏡像工具從 Go 移植到 Rust 讓進行確定性模擬測試 (DST) 變得可能,而無需與 Go 的執行環境作鬥爭。此外,缺乏 GC 停頓使得 Rust 在低延遲需求方面成為更優越的選擇,儘管有人認為 Go 的 GC 停頓現在已經足夠短且可預測,足以應付絕大多數的 Web 服務。

Rust 的維運成本

雖然 Rust 的技術優勢很明顯,但維運與人因工程(ergonomic)成本可能相當可觀,特別是對於 Web 後端而言。

「依賴爆炸"

Go 因其強大的標準函式庫而受到讚譽,這減少了對第三方套件的依賴。相比之下,Rust 的標準函式庫刻意保持精簡,將大部分功能推向「crates」。這可能導致龐大的依賴樹。一位開發者強調了一個專案,儘管僅要求了 rusqliteclap 等少數核心函式庫,其依賴數量卻增長到了 400 多個。

編譯時間與 AI 開發速度

在 AI 輔助編碼的時代,編譯速度已成為開發者開發速度(velocity)的關鍵指標。Go 近乎即時的編譯速度允許快速的迭代週期。Rust 複雜的借用檢查與優化階段導致編譯時間顯著變慢。這產生了一個「軟體工程經濟學」問題:如果一個 AI agent 正在進行編譯-測試週期,那麼對於標準的 CRUD 應用程式而言,等待 Rust 編譯的 token 成本與時間成本可能會超過最終二進位檔在效能上的 20-50% 提升。

並行處理:Goroutines vs. Async Rust

並行處理是這兩種語言在哲學上分歧最劇烈的地方:

  • Go: 使用 goroutines 和 channels,提供了一種輕量級、搶佔式調度模型,易於理解且對於 I/O 密集型 Web 服務非常有效。
  • Rust: 採用 async/await 模型(通常由 tokio 執行環境驅動)。雖然功能強大,但它更複雜且缺乏 Go 的內建搶佔機制。在 async Rust 任務中的長時間 CPU 密集型任務可能會使執行器(executor)飢餓,除非明確地將其卸載到 spawn_blockingrayon

綜合分析:何時遷移?

資深工程師的共識建議,遷移的決定應該基於「失敗的成本」與「迭代的成本」之間的權衡。

"對於你而言,生產環境事故的成本與迭代變慢的成本,哪一個更高?"

在以下情況選擇 Go:

  • 你正在構建標準的 Web 服務或 CRUD 應用程式。
  • 優先考慮快速迭代與快速部署週期,而非原始執行速度。
  • 你偏好受管執行環境以減少記憶體管理的認知負荷。
  • 你想要一個穩定、全面的標準函式庫,且第三方依賴極少。

在以下情況選擇 Rust:

  • 你正在構建關鍵基礎設施,其中單一的記憶體洩漏或資料競爭是災難性的。
  • 你需要確定性的執行或對記憶體佈局的絕對控制權。
  • 你正在構建 CPU 密集型工具、編譯器或高效能代理伺服器。
  • 你的團隊對於借用檢查器與 async 生態系統的陡峭學習曲線感到自在。

Sources