欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Rust常用特型之Drop特型

 更新時間:2024年03月14日 11:41:53   作者:AiMateZero  
本文主要介紹了Rust常用特型之Drop特型,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

Rust常用特型之Drop特型.md在Rust標準庫中,存在很多常用的工具類特型,它們能幫助我們寫出更具有Rust風格的代碼。

今天,我們主要學習Drop特型。

(注:本文更多的是對《Programing Rust 2nd Edition》的自己翻譯和理解,并不是原創(chuàng))

一、什么是Drop

當一個值不再擁有owner時(在Rust中每個值都有一個owner,并且最多只有一個owner),我們說Rust釋放/清理(Drop)了該值。釋放一個值通常意味著也需要一并釋放它占用的其它資源,例如堆存儲。釋放可以發(fā)生在多種場合:例如變量超出作用域,表達式語句的結尾,截斷一個向量并移除末尾的值等。

接下來的內容中,清理和釋放表達的是同一個含義,均為drop的意思。

通常情況下,Rust會自動為你清理值。例如如下代碼:

struct Appellation {
  name: String,
  nicknames: Vec<String>
}

這里我們來復習一下Vec<T>的有關知識。

一個Vec<T>由三個值構成, 第一個值是指針,它指向在堆上為元素分配的緩沖區(qū)。 該緩沖區(qū)由Vec<T>本身擁有。第二值是緩沖區(qū)的容量Cap。第三個值是當前元素的個數length。它是一個胖指針。當緩沖區(qū)的大小達到它的容量時,再增加元素會重新分配一個更大的緩沖區(qū),并將原來的元素復制過去,同時更新向量的指針,容量和長度值,最后釋放舊的緩沖區(qū)。

一個Appellation對象即包含了堆上的字符串內容(對應的name字段),又包含了堆上的向量元素緩沖區(qū)(對應nicknames字段)。當這個對象釋放時,Rust會小心清理所有資源,并不需要你自己做任何處理。然而,如果你愿意,你也可以通過實現std::ops::Drop特型來自定義你的類型的清理方式這里為什么有個你的類型呢?因為Rust不允許特型和類型都是外部的,必須有一個是本地的。此時Drop特型已經是外來的(相對于你的代碼),因此類型必須是本地定義的。

Drop特型的定義為:

trait Drop {
  fn drop(&mut self);
}

個人理解,未必正確

我們可以看到,該特型僅有一個drop函數,注意它的參數類型是&mut,因為我們要做相關清理工作,因此必須是可變的。如果參數是mut self會怎么樣?那么相當于值轉移到本函數中了,在本函數處理完畢后該值的owner就不存在了,此時又到了調用drop的場景,從而形成無限循環(huán),所以參數類型必定為&mut。

二、Drop特型的實現

當一個值被清理時,如果它實現了Drop特型,那么Rust會自動調用它的drop方法。該調用發(fā)生在清理它的內部元素或者字段之前。這說明用戶自定義的drop函數有第一優(yōu)先權。當然這種隱匿調用也是調用drop函數的唯一方式,如果你手動調用它,那么Rust會標記為一個錯誤。

這里也印證了上面提到的drop函數的參數類型&mut,因為發(fā)生在清理它的內部元素之前,所以該值在此時必須保留,所以不能是mut self。也正因為如此,這個值一定是初始化過的(應該是變量初始化過)。

上面Appellation類型的一個示例Drop實現代碼為:

impl Drop for Appellation {
  fn drop(&mut self) {
    print!("Dropping {}", self.name);
    if !self.nicknames.is_empty() {
    	print!(" (AKA {})", self.nicknames.join(", "));
    }
    println!("");
  }
}

假定實現為上述代碼,那么我們可以接下來寫一段測試代碼:

{
  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");
}

那么運行得到的結果是什么呢?我們一行一行來分析代碼:

  • 1-6行,定義了一個類型為 Appellation 的mut變量a ,它的值在定義時已經初始化了
  • 第7行,打印開始重新賦值信息before assignment并換行。
  • 第8行,將a重新賦值,此時a原來的值被拋棄了,沒有owner了,因此符合清理的條件,Rust會自動對其進行清理,在該值上調用drop函數
  • drop函數首先打印值的name,這里應該是Dropping Zeus。注意這里是print!,未換行。
  • 接下來,因為nicknames不為空,將它的元素使用,連接起來,所以應該為 (AKA cloud collector,king of the gods)。注意這里是print!,未換行,因此是接在Dropping Zeus之后。
  • 接下來println!("");目的是產生換行。
  • drop函數調用完畢,接下來回到示例代碼第9行,打印at end of block
  • 第10行,示例代碼結束,變量a超過作用域,在此釋放,也會調用其drop函數。
  • 再次回到drop函數,打印對象名稱,此時應該為Dropping Hera。
  • 因為第二個Appellation值的nicknames字段為空向量,所以不再打印AKA相關。
  • 再次換行。

最終輸出結果為:

before assignment
Dropping Zeus (AKA cloud collector, king of the gods)
at end of block
Dropping Hera

上面的代碼中,類型為Appellation的變量a前后有兩個不同的值,因此觸發(fā)了兩次清理。第一次清理發(fā)生在重新賦值時,此時第一個值被拋棄,變成了無owner,所以觸發(fā)清理。第二次發(fā)生在代碼塊結束 ,此時a超出作用域,也觸發(fā)清理。

可以看到,我們的清理并沒有清除掉內部元素占用的資源,這是Rust會在接下來自動處理的,我們的工作主要是作一些額外的處理。

針對這個問題,書中已經給了明確答案。Rust自動清理內部元素,而內部元素也會自動清理自己。例如Vec類型也實現了Drop特型,它會清理掉它的內部元素并釋放它占用的堆上的緩沖區(qū)。字符串內部使用Vec<u8>來保存它的文本,因此字符串并不需要自己實現Drop特型(Vec<T>實現了就可以),向量本身來處理這些字符的釋放。相同的原則應用于Appellation值,向量的Drop實現會自動釋放它的元素。對于 Appellation值本身,它也有一個owner,它可以是本地臨時變量或者某些數據結構,這個變量對釋放它負責。

注意:

當一個變量的值被移走時,該變量就是未初始化的,因此在超過作用域時并不會觸發(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?");

根據complicated_condition返回值的不同,p或者q其中的一個在代碼結束時會擁有這個Appellation值,另一個變量是未初始化。這也決定了他們是在最后的println!之前還是之后drop(這是因為q的作用域在println!之前結束而p的作用域在這之后結束)。雖然在Rust中一個值可以從一個變量移到另一個變量,但是只會清理一次。

通常情況下,你不需要給自己定義的類型實現Drop特型,除非它擁有了Rust所不能自動處理的資源。例如,在Unix系統(tǒng)中,Rust標準為使用如下的內部結構來代表操作系統(tǒng)文件描述:

struct FileDesc {
  fd: c_int,
}

其中fd字段代表的文件描述數字在程序結束的時候應該關掉。標準庫因此為之實現了Drop特型來關掉它。

impl Drop for FileDesc {
  fn drop(&mut self) {
    let _ = unsafe { libc::close(self.fd) };
  }
}

這里,libc::close是C語言庫的close函數的Rust名字,Rust只能在unsafe代碼塊中調用C語言的函數。

知識點:

如果一個類型實現了Drop特型,那么它不能再實現Copy特型。如果一個類型是Copy類型,那么意味著簡單的字節(jié)復制就夠了,這樣可能會導致兩個變量會擁有同一塊數據。但是如果兩個變量都面臨清理時,相同的數據就會清理兩次,這是一個錯誤。就好像上面的FileDesc例子,如果它實現了Copy特型,那么另一個變量也會關閉相同的fd數字,顯然這是一個錯誤。

進一步思考,如果把Copy換成Clone呢?經過測試是沒有問題的。

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);
}

運行結果為:

over:Unit
in drop
in drop

有人說那如果把FileDesc設計為實現Clone特型不一樣么?其實還真不一樣,因為fd字段的排它性,所以把它設計為Clone是錯誤的。只有可以復制的資源才能設計為實現Clone特型,這個問題其實是Clone特型的設計問題了,而不是Drop特型的問題。

有人說如果兩個變量都包含對同一塊數據的引用,那么是不是清理兩次呢?顯然不是,引用不擁有值,不會觸發(fā)清理。

標準前置還包含了一個drip函數用來清理一個值,但是它的定義相當魔幻:

fn drop<T>(_x: T) { }

從代碼中可以看出,它接收一個值并且獲得了該值的owner。在函數結束時_x超出了作用域而會被Rust正常的清理掉。這里只是提供了一個便利功能,并不是手動調用值的drop函數。

到此這篇關于Rust常用特型之Drop特型的文章就介紹到這了,更多相關Rust Drop特型內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • rust的package,crate,module示例解析

    rust的package,crate,module示例解析

    rust提供了非常優(yōu)秀的包管理器cargo,我們可以使用crate,module,package來組織代碼,這篇文章主要介紹了rust的package,crate,module相關知識,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • 詳解Rust Substrate框架中的Runtime

    詳解Rust Substrate框架中的Runtime

    ubstrate是一個區(qū)塊鏈開發(fā)框架,它提供了一系列模塊化和可擴展的組件,可以幫助開發(fā)人員快速構建自定義區(qū)塊鏈。 Runtime是Substrate區(qū)塊鏈的核心部分,文中有詳細的代碼示例,需要的朋友可以參考下
    2023-05-05
  • Rust語言之Prometheus系統(tǒng)監(jiān)控工具包的使用詳解

    Rust語言之Prometheus系統(tǒng)監(jiān)控工具包的使用詳解

    Prometheus?是一個開源的系統(tǒng)監(jiān)控和警報工具包,最初是由SoundCloud構建的,隨著時間的發(fā)展,Prometheus已經具有適用于各種使用場景的版本,為了開發(fā)者方便開發(fā),更是有各種語言版本的Prometheus的開發(fā)工具包,本文主要介紹Rust版本的Prometheus開發(fā)工具包
    2023-10-10
  • 解析Rust?struct?中的生命周期

    解析Rust?struct?中的生命周期

    rust?的生命周期保證了內存的安全性,同時也增加了開發(fā)者的心智負擔。是在上線之前多費心思寫代碼,還是在上線以后忙忙活活查問題,這是個?trade?off?問題,這篇文章主要介紹了Rust?struct?中的生命周期,需要的朋友可以參考下
    2022-10-10
  • 利用Rust實現一個簡單的Ping應用

    利用Rust實現一個簡單的Ping應用

    這兩年Rust火的一塌糊涂,甚至都燒到了前端,再不學習怕是要落伍了。最近翻了翻文檔,寫了個簡單的Ping應用練練手,感興趣的小伙伴可以了解一下
    2022-12-12
  • Rust 枚舉和模式匹配的實現

    Rust 枚舉和模式匹配的實現

    枚舉是 Rust 中非常重要的復合類型,也是最強大的復合類型之一,廣泛用于屬性配置、錯誤處理、分支流程、類型聚合等場景中,本文就來介紹一下Rust 枚舉和模式匹配,感興趣的可以了解一下
    2023-12-12
  • Rust中用enum實現多參數Hook機制完整代碼

    Rust中用enum實現多參數Hook機制完整代碼

    在 Rust 中,如果想為enum實現一個帶多參數的 Hook 機制,可以結合模式匹配和枚舉來處理,這種方式可以擴展到支持不同類型的輸入參數和邏輯處理,下面通過示例代碼介紹Rust中用enum實現多參數Hook機制,感興趣的朋友一起看看吧
    2024-12-12
  • 淺談Rust?+=?運算符與?MIR?應用

    淺談Rust?+=?運算符與?MIR?應用

    這篇文章主要介紹了Rust?+=?運算符與?MIR?應用,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-01-01
  • 使用vscode配置Rust運行環(huán)境全過程

    使用vscode配置Rust運行環(huán)境全過程

    VS Code對Rust有著較完備的支持,這篇文章主要給大家介紹了關于使用vscode配置Rust運行環(huán)境的相關資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2023-06-06
  • Rust 累計時間長度的操作方法

    Rust 累計時間長度的操作方法

    在Rust中,如果你想要記錄累計時間,通??梢允褂脴藴蕩熘械膕td::time::Duration類型,這篇文章主要介紹了Rust如何累計時間長度,需要的朋友可以參考下
    2024-05-05

最新評論