Rust 使用 Drop Trait 運(yùn)行清理代碼

2023-03-22 15:13 更新
ch15-03-drop.md
commit d44317c3122b44fb713aba66cc295dee3453b24b

對(duì)于智能指針模式來說第二個(gè)重要的 trait 是 Drop,其允許我們?cè)谥狄x開作用域時(shí)執(zhí)行一些代碼??梢詾槿魏晤愋吞峁?nbsp;Drop trait 的實(shí)現(xiàn),同時(shí)所指定的代碼被用于釋放類似于文件或網(wǎng)絡(luò)連接的資源。我們?cè)谥悄苤羔樕舷挛闹杏懻?nbsp;Drop 是因?yàn)槠涔δ軒缀蹩偸怯糜趯?shí)現(xiàn)智能指針。例如,當(dāng) Box<T> 被丟棄時(shí)會(huì)釋放 box 指向的堆空間。

在其他一些語言中,我們不得不記住在每次使用完智能指針實(shí)例后調(diào)用清理內(nèi)存或資源的代碼。如果忘記的話,運(yùn)行代碼的系統(tǒng)可能會(huì)因?yàn)樨?fù)荷過重而崩潰。在 Rust 中,可以指定每當(dāng)值離開作用域時(shí)被執(zhí)行的代碼,編譯器會(huì)自動(dòng)插入這些代碼。于是我們就不需要在程序中到處編寫在實(shí)例結(jié)束時(shí)清理這些變量的代碼 —— 而且還不會(huì)泄漏資源。

指定在值離開作用域時(shí)應(yīng)該執(zhí)行的代碼的方式是實(shí)現(xiàn) Drop trait。Drop trait 要求實(shí)現(xiàn)一個(gè)叫做 drop 的方法,它獲取一個(gè) self 的可變引用。為了能夠看出 Rust 何時(shí)調(diào)用 drop,讓我們暫時(shí)使用 println! 語句實(shí)現(xiàn) drop

示例 15-14 展示了唯一定制功能就是當(dāng)其實(shí)例離開作用域時(shí),打印出 Dropping CustomSmartPointer! 的結(jié)構(gòu)體 CustomSmartPointer。這會(huì)演示 Rust 何時(shí)運(yùn)行 drop 函數(shù):

文件名: src/main.rs

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}

示例 15-14:結(jié)構(gòu)體 CustomSmartPointer,其實(shí)現(xiàn)了放置清理代碼的 Drop trait

Drop trait 包含在 prelude 中,所以無需導(dǎo)入它。我們?cè)?nbsp;CustomSmartPointer 上實(shí)現(xiàn)了 Drop trait,并提供了一個(gè)調(diào)用 println! 的 drop 方法實(shí)現(xiàn)。drop 函數(shù)體是放置任何當(dāng)類型實(shí)例離開作用域時(shí)期望運(yùn)行的邏輯的地方。這里選擇打印一些文本以展示 Rust 何時(shí)調(diào)用 drop。

在 main 中,我們新建了兩個(gè) CustomSmartPointer 實(shí)例并打印出了 CustomSmartPointer created.。在 main 的結(jié)尾,CustomSmartPointer 的實(shí)例會(huì)離開作用域,而 Rust 會(huì)調(diào)用放置于 drop 方法中的代碼,打印出最后的信息。注意無需顯式調(diào)用 drop 方法:

當(dāng)運(yùn)行這個(gè)程序,會(huì)出現(xiàn)如下輸出:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished dev [unoptimized + debuginfo] target(s) in 0.60s
     Running `target/debug/drop-example`
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

當(dāng)實(shí)例離開作用域 Rust 會(huì)自動(dòng)調(diào)用 drop,并調(diào)用我們指定的代碼。變量以被創(chuàng)建時(shí)相反的順序被丟棄,所以 d 在 c 之前被丟棄。這個(gè)例子剛好給了我們一個(gè) drop 方法如何工作的可視化指導(dǎo),不過通常需要指定類型所需執(zhí)行的清理代碼而不是打印信息。

通過 std::mem::drop 提早丟棄值

不幸的是,我們并不能直截了當(dāng)?shù)慕?nbsp;drop 這個(gè)功能。通常也不需要禁用 drop ;整個(gè) Drop trait 存在的意義在于其是自動(dòng)處理的。然而,有時(shí)你可能需要提早清理某個(gè)值。一個(gè)例子是當(dāng)使用智能指針管理鎖時(shí);你可能希望強(qiáng)制運(yùn)行 drop 方法來釋放鎖以便作用域中的其他代碼可以獲取鎖。Rust 并不允許我們主動(dòng)調(diào)用 Drop trait 的 drop 方法;當(dāng)我們希望在作用域結(jié)束之前就強(qiáng)制釋放變量的話,我們應(yīng)該使用的是由標(biāo)準(zhǔn)庫提供的 std::mem::drop。

如果我們像是示例 15-14 那樣嘗試調(diào)用 Drop trait 的 drop 方法,就會(huì)得到像示例 15-15 那樣的編譯錯(cuò)誤:

文件名: src/main.rs

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    c.drop();
    println!("CustomSmartPointer dropped before the end of main.");
}

示例 15-15:嘗試手動(dòng)調(diào)用 Drop trait 的 drop 方法提早清理

如果嘗試編譯代碼會(huì)得到如下錯(cuò)誤:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
  --> src/main.rs:16:7
   |
16 |     c.drop();
   |     --^^^^--
   |     | |
   |     | explicit destructor calls not allowed
   |     help: consider using `drop` function: `drop(c)`

For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` due to previous error

錯(cuò)誤信息表明不允許顯式調(diào)用 drop。錯(cuò)誤信息使用了術(shù)語 析構(gòu)函數(shù)destructor),這是一個(gè)清理實(shí)例的函數(shù)的通用編程概念。析構(gòu)函數(shù) 對(duì)應(yīng)創(chuàng)建實(shí)例的 構(gòu)造函數(shù)。Rust 中的 drop 函數(shù)就是這么一個(gè)析構(gòu)函數(shù)。

Rust 不允許我們顯式調(diào)用 drop 因?yàn)?Rust 仍然會(huì)在 main 的結(jié)尾對(duì)值自動(dòng)調(diào)用 drop,這會(huì)導(dǎo)致一個(gè) double free 錯(cuò)誤,因?yàn)?Rust 會(huì)嘗試清理相同的值兩次。

因?yàn)椴荒芙卯?dāng)值離開作用域時(shí)自動(dòng)插入的 drop,并且不能顯式調(diào)用 drop,如果我們需要強(qiáng)制提早清理值,可以使用 std::mem::drop 函數(shù)。

std::mem::drop 函數(shù)不同于 Drop trait 中的 drop 方法??梢酝ㄟ^傳遞希望提早強(qiáng)制丟棄的值作為參數(shù)。std::mem::drop 位于 prelude,所以我們可以修改示例 15-15 中的 main 來調(diào)用 drop 函數(shù)。如示例 15-16 所示:

文件名: src/main.rs

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    drop(c);
    println!("CustomSmartPointer dropped before the end of main.");
}

示例 15-16: 在值離開作用域之前調(diào)用 std::mem::drop 顯式清理

運(yùn)行這段代碼會(huì)打印出如下:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished dev [unoptimized + debuginfo] target(s) in 0.73s
     Running `target/debug/drop-example`
CustomSmartPointer created.
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.

Dropping CustomSmartPointer with data `some data`! 出現(xiàn)在 CustomSmartPointer created. 和 CustomSmartPointer dropped before the end of main. 之間,表明了 drop 方法被調(diào)用了并在此丟棄了 c。

Drop trait 實(shí)現(xiàn)中指定的代碼可以用于許多方面,來使得清理變得方便和安全:比如可以用其創(chuàng)建我們自己的內(nèi)存分配器!通過 Drop trait 和 Rust 所有權(quán)系統(tǒng),你無需擔(dān)心之后的代碼清理,Rust 會(huì)自動(dòng)考慮這些問題。

我們也無需擔(dān)心意外的清理掉仍在使用的值,這會(huì)造成編譯器錯(cuò)誤:所有權(quán)系統(tǒng)確保引用總是有效的,也會(huì)確保 drop 只會(huì)在值不再被使用時(shí)被調(diào)用一次。

現(xiàn)在我們學(xué)習(xí)了 Box<T> 和一些智能指針的特性,讓我們聊聊標(biāo)準(zhǔn)庫中定義的其他幾種智能指針。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)