深入 Rust

Drop 特徵(the Drop trait)

當一個值擁有一項資源——堆積記憶體、一個開啟的檔案、一條網路連線、一個持有的鎖——在你用完時,總得有東西去釋放它。在 C 裡你親手呼叫 free() 或 close(),並祈禱自己永不忘記;忘了就洩漏資源,而釋放兩次、或在每條錯誤路徑上都釋放,本身又是錯誤的來源。Rust 把清理綁到所有權與範圍:當一個擁有值離開範圍時,它的清理自動執行。Drop 特徵就是你為自己的型別說出那個清理「是什麼」的方式——它是 Rust 的解構子。

你以寫 impl Drop for YourType { fn drop(&mut self) { /* 釋放資源 */ } } 來實作它。這個方法的本體就是你的清理程式碼,而你不去呼叫它——編譯器會在值的範圍結束的確切那一點自動插入一個對它的呼叫,並依宣告的相反順序進行(最後建立的值最先被 drop)。這和 C++ 解構子是同一個 RAII 想法:在一個類似建構子的步驟取得資源、並在 drop 裡釋放它,意味資源被綁到值的生命週期,而範圍的結束「就是」那次釋放,所以你不可能忘記它,且它在每條離開路徑上都會發生,包括提早返回與恐慌。一個 File 在它 drop 時關閉自己;一個 MutexGuard 在它 drop 時解鎖;一個 Box 在它 drop 時釋放它的堆積配置;一個 Rc 在它 drop 時遞減計數。你很少為純資料親寫 Drop(欄位自己的 drop 會遞迴處理它)——你在你的型別管理一項編譯器尚不知如何釋放的資源時才寫它,例如一個來自 FFI 呼叫的原始把手。

為何重要:Drop 加上所有權,正是 Rust 在沒有垃圾回收器下給出決定性、自動的資源管理的原因——清理發生在一個已知的點(範圍結束),而非在某個回收器決定要跑的時候。有幾個誠實的細節要緊。你不能自己呼叫 drop(&mut self),也不能 drop 一個值兩次;要提早釋放某物,你把它傳給 std::mem::drop 函式(它只是取得所有權並讓範圍立刻結束)。Drop 在恐慌時也會跑(這個「展開」正是即使一條執行緒正在崩潰、鎖也能被釋放的方式),但一個本身在展開期間恐慌的 drop 方法會中止程式。而一個 Drop 型別不是 Copy——移動它會轉移那唯一的清理責任,絕不複製它。最後,Rust 並「不保證」drop 一定會跑:故意洩漏一個值(std::mem::forget,或一個 Rc 的參考環)會跳過它,所以 Drop 是用來在正常路徑上可靠地釋放資源,而非一個「永遠不會被跳過」的硬性安全保證。

struct Guard { name: String } impl Drop for Guard { fn drop(&mut self) { println!("releasing {}", self.name); } } fn main() { let _a = Guard { name: String::from("a") }; let _b = Guard { name: String::from("b") }; println!("working"); } // 範圍結束 -> drop 以「相反」順序執行: // 印出:working / releasing b / releasing a

Drop 在範圍結束時自動執行,依相反宣告順序——清理綁到所有權,在每條離開路徑上都跑,包括恐慌。

你不能自己呼叫 .drop() 或 drop 一個值兩次;用 std::mem::drop 來提早釋放。Rust「不保證」drop 一定執行——std::mem::forget 或一個 Rc 環會跳過它——所以 Drop 在正常路徑上可靠地釋放,卻不是一個打不破的安全保證。

又稱
DropdestructorRAII in Rustcleanup code解構清理資源釋放