Rust 最強的賣點,是在編譯期排除資料競爭、懸空指標與許多記憶體錯誤;但「通過借用檢查器」從來不等於「可以安心值班」。corrode 的原文把焦點從型別安全移到生產環境的失敗模型:panic、資源耗盡、外部服務失效、供應鏈、容器權限與健康檢查。這個切角值得肯定,因為它提醒團隊,語言安全只覆蓋可靠性工程的一部分。原文:https://corrode.dev/blog/hardening-rust/
編譯器保證之外,還有四層風險
來源事實:原文列出的問題大致可分為四層。第一是程式內部的失敗語意,例如 panic 究竟只終止一條執行緒,還是中止整個程序;第二是輸入、佇列、連線池與記憶體等資源是否有上限;第三是資料庫、API、檔案系統及作業系統權限等外部邊界;第四才是部署後的監控、健康檢查與優雅關機。
這些風險不違反 Rust 的安全承諾。所有權系統可以阻止某些不安全的記憶體存取,卻不會替產品決定請求本文應限制在 1 MB 或 100 MB,也不知道一筆工作應等外部 API 幾秒。合法 Rust 程式仍可能無限排隊、遞迴耗盡堆疊、因配置記憶體失敗而退出,或在下游服務故障時形成重試風暴。
panic 不是單一行為,也不是一般例外處理
Rust 官方參考文件確認,標準函式庫具有 unwind 與 abort 兩種 panic handler:前者展開堆疊並執行沿途物件的 Drop,後者直接終止程序;catch_unwind 只能捕捉展開型 panic,官方也明確不建議把它當成通用的 try/catch。可預期、可處理的失敗仍應透過 Result 表達。官方說明:https://doc.rust-lang.org/stable/reference/panic.html、https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
在多執行緒程式中,子執行緒展開至根部後可以退出,而呼叫端透過 JoinHandle::join 收到 Err;程序其他部分可能繼續運作。這既是隔離機制,也是風險:背景工作者死亡而主服務仍回應健康狀態,可能造成「看似在線、功能已殘缺」的半失效。合理推論是,團隊不能只問「會不會 panic」,而要先定義每一種 panic 所代表的不變量範圍:單一請求失敗可以隔離;若共享狀態已不可信,繼續服務反而更危險。
原文有一處重要限制需要修正。它聲稱 panic hook 只在 unwind 時執行、使用 abort 策略便不會執行;Rust 官方 set_hook 文件則明確表示,hook 在 panic runtime 之前觸發,因此適用於 abort 與 unwind 兩種 runtime。真正需要保守看待的是「直接中止程序」的故障:例如標準函式庫目前對一般連結 std 的配置失敗,預設會輸出訊息後 abort,不能把 panic hook 當成必然執行的最後防線。資料來源:https://doc.rust-lang.org/stable/std/panic/fn.set_hook.html、https://doc.rust-lang.org/stable/std/alloc/fn.handle_alloc_error.html
真正的商業邏輯,是把災難轉成可控制的拒絕
原文最有價值的主張,不是再加一套工具,而是替所有無界資源設定預算:請求大小、佇列深度、同時連線數、執行緒與資料庫連線池,以及每個外部呼叫的逾時。這些限制會讓尖峰時段出現明確的拒絕或降級,卻能避免記憶體耗盡、延遲無限堆積與整個程序遭拖垮。作者觀點是:可靠性不是追求「永不失敗」,而是決定失敗停在哪一層、誰能觀察到,以及系統如何恢復。
同一邏輯也適用於 Kubernetes。readiness probe 失敗時,Pod 會從相符 Service 的 EndpointSlice 可用端點中移除;liveness probe 超過容許失敗次數後,kubelet 才重啟容器。官方同時警告,錯誤的 liveness 設計可能在高負載時製造連鎖重啟。因此,「資料庫暫時變慢」通常不該不加判斷地等同於「程序必須被殺掉」。官方資料:https://kubernetes.io/docs/concepts/workloads/pods/probes/
為何測試通過仍不足以證明可上線
來源事實:Cargo 的 dev、test 與 release 是不同 profile。官方文件顯示,debug-assertions 控制 debug_assert! 與相關條件編譯,overflow-checks 則決定整數溢位是否 panic;這些設定可以自訂,不能簡化成所有 release 程式必然採同一行為。實務上,CI 除了快速的預設測試,還應讓涉及算術、unsafe、FFI 與 release-only 設定的關鍵流程執行 cargo test --release。官方資料:https://doc.rust-lang.org/cargo/reference/profiles.html
依賴也屬於執行期風險。原文建議使用 cargo-audit 與 cargo-deny;其中 RustSec 的說明證實,cargo-audit 會以 RustSec Advisory Database 檢查專案依賴。不過掃描結果仍需判斷受影響功能、平台與可升級性,不能把「CI 顯示零警報」誤當成完整供應鏈保證。來源:https://github.com/rustsec/rustsec/blob/main/cargo-audit/README.md
容易忽略的限制與台灣團隊的意義
原文涵蓋 distroless 映像、Landlock、安全配置器、Miri、模糊測試與斷路器,但它們不是可直接複製的通用清單。Landlock 受 Linux 核心與部署環境限制;最小映像仍須提供二進位檔實際動態連結的函式庫;配置器會改變延遲特性;panic 回報還可能把使用者輸入、token、電子郵件或內部路徑送進日誌。硬化措施本身也需要相容性測試、效能量測與個資遮罩。
作者觀點:對台灣後端團隊而言,重點不是 Rust 是否比既有語言「更安全」,而是導入時是否把維運責任一併估價。若團隊只投資改寫服務,卻沒有 release profile 測試、容量上限、依賴稽核、告警與故障演練,省下的記憶體除錯成本可能轉成夜間值班與事故定位成本。Rust 的優勢仍然成立,但必須與平台工程共同落地。
後續應觀察的指標
- panic 是按請求、執行緒還是程序統計,是否能辨識已死亡但未被監控的背景工作者。
- release 測試、unsafe/FFI 測試與模糊測試是否真的進入 CI,而非只存在文件中。
- 佇列使用率、逾時率、拒絕率與記憶體高水位是否有明確容量門檻。
- readiness 與 liveness 是否反映不同故障,並經過高負載及下游中斷演練。
- panic 與崩潰報告是否保留版本、請求識別碼等診斷線索,同時移除祕密與個資。
- 依賴弱點從發現、判讀到修補需要多久,以及例外接受是否有期限與負責人。
Rust 能把一大類錯誤擋在部署之前;生產硬化則要處理剩下那些編譯器無從理解的現實。成熟的判準不是「編譯成功」,而是每個失敗都有邊界、每項資源都有上限、每次降級都能被看見,且恢復流程已經演練過。