邁向 Rust

Rust 防住若干錯誤家族,但不是萬靈丹(not a silver bullet)

在認識所有權與借用檢查器之後,很容易就帶著「Rust 代表不再有錯誤」的信念離開。並非如此,而那樣推銷它對這門語言是種傷害。Rust 真正交付的,既鋒利又有限:在它的安全子集裡,它在編譯期消除一個特定、代價高昂的錯誤家族——釋放後使用、重複釋放、懸置參考、緩衝區越界、與資料競爭。這些並不小;它們是 C 與 C++ 程式庫中很大一部分安全漏洞的來源。從構造上移除它們是一項真切而重要的勝利。但那是一類錯誤,不是所有錯誤。

對剩下的部分要精確。Rust 不阻止邏輯錯誤——乾淨編譯、安全執行的程式碼,仍可能算出錯誤的答案,因為你邏輯的正確性不是型別系統會檢查的東西。它不防止死結:兩條執行緒可以各自永遠等著對方持有的鎖,那個程式完全記憶體安全,卻完全卡死。它不防止所有的記憶體洩漏——你可以讓一個值永遠活著、或刻意呼叫 forget,而 Rc 裡的參考循環也會洩漏。它不阻止恐慌、不阻止 release 建構中整數溢位無聲環繞、也不阻止資源耗盡。而且這份安全保證只涵蓋安全程式碼:在 unsafe 區塊內、或透過外部函式介面呼叫 C 時,你可以造成 Rust 在別處所禁止的、一模一樣的未定義行為——而單一個 unsafe 錯誤就可能損壞程式裡任何地方的記憶體。

為何這份誠實的說法重要:它告訴你該在哪裡保持戒備。Rust 把你從持續擔心某一類記憶體錯誤中解放出來,讓你能把那份注意力花在它抓不到的錯誤上——你的邏輯、你的並行設計、你的錯誤處理、你寫的任何 unsafe 的正確性。把 Rust 當成一個讓整類失誤在安全程式碼中變得不可能的強大工具,而非軟體正確性的保證。正確的心態是謹慎工程師的心態:對編譯器抓到的錯誤心存感激、對它永遠抓不到的那些睜大眼睛,並且仍然寫測試、審查 unsafe 區塊、認真思考你的程式究竟應該做什麼。

// 編譯通過、記憶體安全、卻仍然錯誤/卡死: fn area(w: u32, h: u32) -> u32 { w + h } // 邏輯錯誤:應該是 w * h // 兩條執行緒以相反順序取兩把鎖 -> 死結。 // 兩條執行緒都 100% 記憶體安全,也 100% 永遠卡死。

記憶體安全不等於正確:邏輯錯誤與死結照樣編譯、照樣執行。

Rust 在安全程式碼中消除一個特定錯誤家族,而非所有錯誤。邏輯錯誤、死結、故意的洩漏,以及 unsafe 內或跨 FFI 的未定義行為,全都仍然存在——絕不要把 Rust 讀成正確軟體的保證。

又稱
the honest limits of Rustwhat Rust does not fixRust 的誠實界限Rust 不保證什麼