Rust常用特型之Drop特型
Rust常用特型之Drop特型.md在Rust標(biāo)準(zhǔn)庫中,存在很多常用的工具類特型,它們能幫助我們寫出更具有Rust風(fēng)格的代碼。
今天,我們主要學(xué)習(xí)Drop
特型。
(注:本文更多的是對《Programing Rust 2nd Edition》的自己翻譯和理解,并不是原創(chuàng))
一、什么是Drop
當(dāng)一個值不再擁有owner時(在Rust中每個值都有一個owner,并且最多只有一個owner),我們說Rust釋放/清理(Drop)了該值。釋放一個值通常意味著也需要一并釋放它占用的其它資源,例如堆存儲。釋放可以發(fā)生在多種場合:例如變量超出作用域,表達(dá)式語句的結(jié)尾,截斷一個向量并移除末尾的值等。
接下來的內(nèi)容中,清理和釋放表達(dá)的是同一個含義,均為drop的意思。
通常情況下,Rust會自動為你清理值。例如如下代碼:
struct Appellation { name: String, nicknames: Vec<String> }
這里我們來復(fù)習(xí)一下Vec<T>
的有關(guān)知識。
一個Vec<T>
由三個值構(gòu)成, 第一個值是指針,它指向在堆上為元素分配的緩沖區(qū)。 該緩沖區(qū)由Vec<T>
本身擁有。第二值是緩沖區(qū)的容量Cap
。第三個值是當(dāng)前元素的個數(shù)length
。它是一個胖指針。當(dāng)緩沖區(qū)的大小達(dá)到它的容量時,再增加元素會重新分配一個更大的緩沖區(qū),并將原來的元素復(fù)制過去,同時更新向量的指針,容量和長度值,最后釋放舊的緩沖區(qū)。
一個Appellation
對象即包含了堆上的字符串內(nèi)容(對應(yīng)的name字段),又包含了堆上的向量元素緩沖區(qū)(對應(yīng)nicknames
字段)。當(dāng)這個對象釋放時,Rust會小心清理所有資源,并不需要你自己做任何處理。然而,如果你愿意,你也可以通過實(shí)現(xiàn)std::ops::Drop
特型來自定義你的類型的清理方式這里為什么有個你的類型呢?因?yàn)镽ust不允許特型和類型都是外部的,必須有一個是本地的。此時Drop
特型已經(jīng)是外來的(相對于你的代碼),因此類型必須是本地定義的。
Drop特型的定義為:
trait Drop { fn drop(&mut self); }
個人理解,未必正確
我們可以看到,該特型僅有一個drop
函數(shù),注意它的參數(shù)類型是&mut
,因?yàn)槲覀円鱿嚓P(guān)清理工作,因此必須是可變的。如果參數(shù)是mut self
會怎么樣?那么相當(dāng)于值轉(zhuǎn)移到本函數(shù)中了,在本函數(shù)處理完畢后該值的owner
就不存在了,此時又到了調(diào)用drop
的場景,從而形成無限循環(huán),所以參數(shù)類型必定為&mut
。
二、Drop特型的實(shí)現(xiàn)
當(dāng)一個值被清理時,如果它實(shí)現(xiàn)了Drop
特型,那么Rust會自動調(diào)用它的drop
方法。該調(diào)用發(fā)生在清理它的內(nèi)部元素或者字段之前。這說明用戶自定義的drop
函數(shù)有第一優(yōu)先權(quán)。當(dāng)然這種隱匿調(diào)用也是調(diào)用drop
函數(shù)的唯一方式,如果你手動調(diào)用它,那么Rust會標(biāo)記為一個錯誤。
這里也印證了上面提到的drop
函數(shù)的參數(shù)類型&mut
,因?yàn)榘l(fā)生在清理它的內(nèi)部元素之前,所以該值在此時必須保留,所以不能是mut self
。也正因?yàn)槿绱?,這個值一定是初始化過的(應(yīng)該是變量初始化過)。
上面Appellation
類型的一個示例Drop
實(shí)現(xiàn)代碼為:
impl Drop for Appellation { fn drop(&mut self) { print!("Dropping {}", self.name); if !self.nicknames.is_empty() { print!(" (AKA {})", self.nicknames.join(", ")); } println!(""); } }
假定實(shí)現(xiàn)為上述代碼,那么我們可以接下來寫一段測試代碼:
{ let mut a = Appellation { name: "Zeus".to_string(), nicknames: vec!["cloud collector".to_string(), "king of the gods".to_string()] }; println!("before assignment"); a = Appellation { name: "Hera".to_string(), nicknames: vec![]}; println!("at end of block"); }
那么運(yùn)行得到的結(jié)果是什么呢?我們一行一行來分析代碼:
- 1-6行,定義了一個類型為 Appellation 的mut變量a ,它的值在定義時已經(jīng)初始化了
- 第7行,打印開始重新賦值信息
before assignment
并換行。 - 第8行,將a重新賦值,此時a原來的值被拋棄了,沒有
owner
了,因此符合清理的條件,Rust會自動對其進(jìn)行清理,在該值上調(diào)用drop
函數(shù) drop
函數(shù)首先打印值的name
,這里應(yīng)該是Dropping Zeus
。注意這里是print!
,未換行。- 接下來,因?yàn)?code>nicknames不為空,將它的元素使用
,
連接起來,所以應(yīng)該為(AKA cloud collector,king of the gods)
。注意這里是print!
,未換行,因此是接在Dropping Zeus
之后。 - 接下來
println!("");
目的是產(chǎn)生換行。 - drop函數(shù)調(diào)用完畢,接下來回到示例代碼第9行,打印
at end of block
。 - 第10行,示例代碼結(jié)束,變量a超過作用域,在此釋放,也會調(diào)用其
drop
函數(shù)。 - 再次回到
drop
函數(shù),打印對象名稱,此時應(yīng)該為Dropping Hera
。 - 因?yàn)榈诙€
Appellation
值的nicknames
字段為空向量,所以不再打印AKA
相關(guān)。 - 再次換行。
最終輸出結(jié)果為:
before assignment
Dropping Zeus (AKA cloud collector, king of the gods)
at end of block
Dropping Hera
上面的代碼中,類型為Appellation
的變量a前后有兩個不同的值,因此觸發(fā)了兩次清理。第一次清理發(fā)生在重新賦值時,此時第一個值被拋棄,變成了無owner
,所以觸發(fā)清理。第二次發(fā)生在代碼塊結(jié)束 ,此時a超出作用域,也觸發(fā)清理。
可以看到,我們的清理并沒有清除掉內(nèi)部元素占用的資源,這是Rust會在接下來自動處理的,我們的工作主要是作一些額外的處理。
針對這個問題,書中已經(jīng)給了明確答案。Rust自動清理內(nèi)部元素,而內(nèi)部元素也會自動清理自己。例如Vec類型也實(shí)現(xiàn)了Drop
特型,它會清理掉它的內(nèi)部元素并釋放它占用的堆上的緩沖區(qū)。字符串內(nèi)部使用Vec<u8>
來保存它的文本,因此字符串并不需要自己實(shí)現(xiàn)Drop
特型(Vec<T>
實(shí)現(xiàn)了就可以),向量本身來處理這些字符的釋放。相同的原則應(yīng)用于Appellation
值,向量的Drop
實(shí)現(xiàn)會自動釋放它的元素。對于 Appellation
值本身,它也有一個owner
,它可以是本地臨時變量或者某些數(shù)據(jù)結(jié)構(gòu),這個變量對釋放它負(fù)責(zé)。
注意:
當(dāng)一個變量的值被移走時,該變量就是未初始化的,因此在超過作用域時并不會觸發(fā)drop,沒有值需要清理。切記,清理的是值不是變量。
下面的一段代碼:
let p; { let q = Appellation { name: "Cardamine hirsuta".to_string(), nicknames: vec!["shotweed".to_string(),"bittercress".to_string()] }; if complicated_condition() { p = q; } } println!("Sproing! What was that?");
根據(jù)complicated_condition
返回值的不同,p或者q其中的一個在代碼結(jié)束時會擁有這個Appellation
值,另一個變量是未初始化。這也決定了他們是在最后的println!
之前還是之后drop
(這是因?yàn)閝的作用域在println!
之前結(jié)束而p的作用域在這之后結(jié)束)。雖然在Rust中一個值可以從一個變量移到另一個變量,但是只會清理一次。
通常情況下,你不需要給自己定義的類型實(shí)現(xiàn)Drop
特型,除非它擁有了Rust所不能自動處理的資源。例如,在Unix
系統(tǒng)中,Rust標(biāo)準(zhǔn)為使用如下的內(nèi)部結(jié)構(gòu)來代表操作系統(tǒng)文件描述:
struct FileDesc { fd: c_int, }
其中fd
字段代表的文件描述數(shù)字在程序結(jié)束的時候應(yīng)該關(guān)掉。標(biāo)準(zhǔn)庫因此為之實(shí)現(xiàn)了Drop
特型來關(guān)掉它。
impl Drop for FileDesc { fn drop(&mut self) { let _ = unsafe { libc::close(self.fd) }; } }
這里,libc::close
是C語言庫的close
函數(shù)的Rust名字,Rust只能在unsafe
代碼塊中調(diào)用C語言的函數(shù)。
知識點(diǎn):
如果一個類型實(shí)現(xiàn)了Drop
特型,那么它不能再實(shí)現(xiàn)Copy
特型。如果一個類型是Copy
類型,那么意味著簡單的字節(jié)復(fù)制就夠了,這樣可能會導(dǎo)致兩個變量會擁有同一塊數(shù)據(jù)。但是如果兩個變量都面臨清理時,相同的數(shù)據(jù)就會清理兩次,這是一個錯誤。就好像上面的FileDesc
例子,如果它實(shí)現(xiàn)了Copy
特型,那么另一個變量也會關(guān)閉相同的fd
數(shù)字,顯然這是一個錯誤。
進(jìn)一步思考,如果把Copy
換成Clone
呢?經(jīng)過測試是沒有問題的。
use std::ops::Drop; // A unit struct without resources #[derive(Debug, Clone)] struct Unit; impl Drop for Unit { fn drop(&mut self) { println!("in drop"); } } fn main() { let a = Unit; let b = a.clone(); println!("over:{:?}",b); }
運(yùn)行結(jié)果為:
over:Unit
in drop
in drop
有人說那如果把FileDesc
設(shè)計(jì)為實(shí)現(xiàn)Clone
特型不一樣么?其實(shí)還真不一樣,因?yàn)?code>fd字段的排它性,所以把它設(shè)計(jì)為Clone
是錯誤的。只有可以復(fù)制的資源才能設(shè)計(jì)為實(shí)現(xiàn)Clone
特型,這個問題其實(shí)是Clone
特型的設(shè)計(jì)問題了,而不是Drop
特型的問題。
有人說如果兩個變量都包含對同一塊數(shù)據(jù)的引用,那么是不是清理兩次呢?顯然不是,引用不擁有值,不會觸發(fā)清理。
標(biāo)準(zhǔn)前置還包含了一個drip
函數(shù)用來清理一個值,但是它的定義相當(dāng)魔幻:
fn drop<T>(_x: T) { }
從代碼中可以看出,它接收一個值并且獲得了該值的owner
。在函數(shù)結(jié)束時_x
超出了作用域而會被Rust正常的清理掉。這里只是提供了一個便利功能,并不是手動調(diào)用值的drop
函數(shù)。
到此這篇關(guān)于Rust常用特型之Drop特型的文章就介紹到這了,更多相關(guān)Rust Drop特型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust語言之Prometheus系統(tǒng)監(jiān)控工具包的使用詳解
Prometheus?是一個開源的系統(tǒng)監(jiān)控和警報工具包,最初是由SoundCloud構(gòu)建的,隨著時間的發(fā)展,Prometheus已經(jīng)具有適用于各種使用場景的版本,為了開發(fā)者方便開發(fā),更是有各種語言版本的Prometheus的開發(fā)工具包,本文主要介紹Rust版本的Prometheus開發(fā)工具包2023-10-10利用Rust實(shí)現(xiàn)一個簡單的Ping應(yīng)用
這兩年Rust火的一塌糊涂,甚至都燒到了前端,再不學(xué)習(xí)怕是要落伍了。最近翻了翻文檔,寫了個簡單的Ping應(yīng)用練練手,感興趣的小伙伴可以了解一下2022-12-12Rust中用enum實(shí)現(xiàn)多參數(shù)Hook機(jī)制完整代碼
在 Rust 中,如果想為enum實(shí)現(xiàn)一個帶多參數(shù)的 Hook 機(jī)制,可以結(jié)合模式匹配和枚舉來處理,這種方式可以擴(kuò)展到支持不同類型的輸入?yún)?shù)和邏輯處理,下面通過示例代碼介紹Rust中用enum實(shí)現(xiàn)多參數(shù)Hook機(jī)制,感興趣的朋友一起看看吧2024-12-12淺談Rust?+=?運(yùn)算符與?MIR?應(yīng)用
這篇文章主要介紹了Rust?+=?運(yùn)算符與?MIR?應(yīng)用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01使用vscode配置Rust運(yùn)行環(huán)境全過程
VS Code對Rust有著較完備的支持,這篇文章主要給大家介紹了關(guān)于使用vscode配置Rust運(yùn)行環(huán)境的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06