Rust之泛型、trait與生命周期

泛型是具體類型或其他屬性的抽象替代。在編寫代碼時,可以直接描述泛型的行為,或者它與其他泛型產生的聯系,而無須知曉它在編譯和運行代碼時采用的具體類型。

1、泛型數據類型:

們可以在聲明函數簽名或結構體等元素時使用泛型,并在隨后搭配不同的具體類型來使用這些元素。

(1)、在函數定義中:

當使用泛型來定義一個函數時,需要將泛型放置在函數簽名中通常用于指定參數和返回值類型的地方。以這種方式編寫的代碼更加靈活,并可以在不引入重復代碼的同時向函數調用者提供更多的功能。
當需要在函數簽名中使用類型參數時,也需要在使用前聲明這個類型參數的名稱。為了定義泛型版本的largest函數,類型名稱的聲明必須被放置在函數名與參數列表之間的一對尖括號<>中,如下所示:

fn largest<T>(list: &[T]) -> T {}

即函數largest擁有泛型參數T,它接收一個名為list的T值切片作為參數,并返回一個同樣擁有類型T的值作為結果。

(2)、在結構體定義中:

可以使用<>語法來定義在一個或多個字段中使用泛型的結構體。示例:

struct Point<T> {x: T,y: T,
}
fn main() {let integer = Point { x: 5, y: 10 };let float = Point { x: 1.0, y: 4.0 };
}

在結構名后的一對尖括號中聲明泛型參數后,就可以在結構體定義中那些通常用于指定具體數據類型的位置使用泛型了。
在定義Point時僅使用了一個泛型,這個定義表明Point結構體對某個類型T是通用的。而無論具體的類型是什么,字段x與y都同時屬于這個類型。但是使用不同的值類型來創建Point實例,那么代碼是無法通過編譯的。示例:

struct Point<T> {x: T,y: T,
}
fn main() {let wont_work = Point { x: 5, y: 4.0 };
}

這段程序無法編譯通過。字段x和y必須是相同的類型,因為它們擁有相同的泛型T。
為了在保持泛型狀態的前提下,讓Point結構體中的x和y能夠被實例化為不同的類型,可以使用多個泛型參數。示例:

struct Point<T, U> {x: T,y: U,
}
fn main() {let both_integer = Point { x: 5, y: 10 };let both_float = Point { x: 1.0, y: 4.0 };let integer_and_float = Point { x: 5, y: 4.0 };
}

(3)、在枚舉定義中:

枚舉定義也可以在它們的變體中存放泛型數據。例如標準庫中提供的Option枚舉:

enum Option<T> {Some(T),None,
}

Option是一個擁有泛型T的枚舉。它擁有兩個變體:持有T類型值的Some變體,以及一個不持有任何值的None變體。Option被用來表示一個值可能存在的抽象概念。也正是因為Option使用了泛型,所以無論這個可能存在的值是什么類型,都可以通過Option來表達這一抽象。
枚舉同樣也可以使用多個泛型參數。例如的Result枚舉:

enum Result<T, E> {Ok(T),Err(E),
}

Result枚舉擁有兩個泛型:T和E。它也同樣擁有兩個變體:持有T類型值的Ok,以及一個持有E類型值的Err。這個定義使得Result枚舉可以很方便地被用在操作可能成功(返回某個T類型的值),也可能失敗(返回某個E類型的錯誤)的場景。

(4)、在方法定義中:

方法也可以在自己的定義中使用泛型。例如結構體Point實現了一個名為x的方法:

struct Point<T> {x: T,y: T,
}
impl<T> Point<T> {fn x(&self) -> &T {&self.x}
}
fn main() {let p = Point { x: 5, y: 10 };println!("p.x = {}", p.x());
}

在上面的代碼中,我們為結構體Point定義了一個名為x的方法,它會返回一個指向字段x中數據的引用。
注意,必須緊跟著impl關鍵字聲明T,以便能夠在實現方法時指定類型Point。通過在impl之后將T聲明為泛型,Rust能夠識別出Point尖括號內的類型是泛型而不是具體類型。

(5)、泛型代碼的性能問題:

Rust實現泛型的方式決定了使用泛型的代碼與使用具體類型的代碼相比不會有任何速度上的差異。
Rust會在編譯時執行泛型代碼的單態化(monomorphization)。單態化是一個在編譯期將泛型代碼轉換為特定代碼的過程,它會將所有使用過的具體類型填入泛型參數從而得到有具體類型的代碼。
在這個過程中,編譯器會尋找所有泛型代碼被調用過的地方,并基于該泛型代碼所使用的具體類型生成代碼。

2、trait:定義共享行為:

trait(特征)被用來向Rust編譯器描述某些特定類型擁有且能夠被其他類型共享的功能,它使我們可以以一種抽象的方式來定義共享特征。還可以使用trait約束泛型參數指定為實現了某些特定行為的類型。

(1)、定義trait:

類型的行為由該類型本身可供調用的方法組成。當在不同的類型上調用了相同的方法時,就稱這些類型共享了相同的行為。trait提供了一種將特定方法簽名組合起來的途徑,它定義了達成某種目的所必需的行為集合。示例:

pub trait Summary {fn summarize(&self) -> String;
}

這里,我們使用了trait關鍵字來聲明tait,緊隨關鍵字的是該trait的名字。在其后的花括號中,聲明了用于定義類型行為的方法簽名。在方法簽名后,省略了花括號及具體的實現,直接使用分號終結了當前的語句。任何想要實現這個trait的類型都需要為上述方法提供自定義行為。編譯器會確保每一個實現了Summary trait的類型都定義了與這個簽名完全一致的summarize方法。
一個trait可以包含多個方法:每個方法簽名占據單獨一行并以分號結尾。

(2)、為類型實現trait:

基于Summary trait定義了所期望的行為,現在就可以在多媒體聚合中依次為每個類型實現這個trait了。示例:

pub struct NewsArticle {pub headline: String,pub location: String,pub author: String,pub content: String,
}
impl Summary for NewsArticle {fn summarize(&self) -> String {format!("{}, by {} ({})", self.headline, self.author, self.location)}
}
pub struct Tweet {pub username: String,pub content: String,pub reply: bool,pub retweet: bool,
}
impl Summary for Tweet {fn summarize(&self) -> String {format!("{}: {}", self.username, self.content)}
}

為類型實現trait與實現普通方法的步驟十分類似。它們的區別在于我們必須在impl關鍵字后提供我們想要實現的trait名,并緊接for關鍵字及當前的類型名。在impl代塊中,我們同樣需要填入trait中
的方法簽名。但在每個簽名的結尾不再使用分號,而是使用花括號并在其中編寫函數體來為這個特定類型實現該trait的方法所應具有的行為。
一旦實現了trait,我們便可以基于NewsArticle和Tweet的實例調用該trait的方法了,正如我們調用普通方法一樣。示例:

let tweet = Tweet {username: String::from("horse_ebooks"),content: String::from("of course, as you probably already know, people"),reply: false,retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());

注意,實現trait有一個限制:只有當trait或類型定義于我們的庫中時,我們才能為該類型實現對應的trait。
我們不能為外部類型實現外部trait。例如,我們不能在aggregator庫內為Vec實現Display trait,因為Display與Vec都被定義在標準庫中,而沒有定義在aggregator庫中。這個限制被稱為孤兒規則 (orphan rule),之所以這么命名是因為它的父類型沒有定義在當前庫中。這一規則也是程序一致性 (coherence)的組成部分,它確保了其他人所編寫的內容不會破壞到你的代碼,反之亦
然。如果沒有這條規則,那么兩個庫可以分別對相同的類型實現相同的trait,Rust將無法確定應該使用哪一個版本。

(3)、默認實現:

有些時候,為trait中的某些或所有方法都提供默認行為非常有用,它使我們無須為每一個類型的實現都提供自定義行為。當我們在為某個特定類型實現trait時,可以選擇保留或重載每個方法的默認行為。示例:

pub trait Summary {fn summarize(&self) -> String {String::from("(Read more...)")}
}

假如我們決定在NewsArticle的實例中使用這種默認實現而不是自定義實現,那么我們可以指定一個空的impl代碼塊:impl Summaryfor NewsArticle {}
為summarize提供一個默認實現并不會影響為Tweet實現Summary時所編寫的代碼。這是因為重載默認實現與實現trait方法的語法完全一致。
還可以在默認實現中調用相同trait中的其他方法,哪怕這些方法沒有默認實現。基于這一規則,trait可以在只需要實現一小部分方法的前提下,提供許多有用的功能。示例:

pub trait Summary {fn summarize_author(&self) -> String;fn summarize(&self) -> String {format!("(Read more from {}...)", self.summarize_author())}
}
impl Summary for Tweet {fn summarize_author(&self) -> String {format!("@{}", self.username)}
}
let tweet = Tweet {username: String::from("horse_ebooks"),content: String::from("of course, as you probably already know, people"),reply: false,retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());

這 段 代 碼 會 打 印 出 1 new tweet: (Read@horse_ebooks...)

(4)、使用trait作為參數:

前面我們為NewsArticle 與 Tweet 類 型 實 現 了Summary trait。我們可以定義一個notify函數來調用其item參數的summarize方法,這里的參數item可以是任何實現了Summary trait的類型。

pub fn notify(item: impl Summary) {println!("Breaking news! {}", item.summarize());
}

我們沒有為item參數指定具體的類型,而是使用了impl關鍵字及對應的trait名稱。這一參數可以接收任何實現了指定trait的類型。在notify的函數體內,我們可以調用來自Summary trait的任何方法,
當然也包括summarize。我們可以在調用notify時向其中傳入任意一個NewsArticle或Tweet實例。假設我們需要接收兩個都實現了Summary的參數,那么使用impl Trait的寫法如下所示:

pub fn notify(item1: impl Summary, item2: impl Summary) {}

假如notify函數需要在調用summarize方法的同時顯示格式化后的item,那么item就必須實現兩個不同的trait:Summary和Display。我們可以使用+語法做到這一點:

pub fn notify(item: impl Summary + Display) {}

(5)、返回實現了trait的類型:

同樣可以在返回值中使用impl Trait語法,用于返回某種實現了trait的類型:

fn returns_summarizable() -> impl Summary {Tweet {username: String::from("horse_ebooks"),content: String::from("of course, as you probably already know, people"),reply: false,retweet: false,}
}

3、使用生命周期保證引用的有效性:

Rust的每個引用都有自己的生命周期(lifetime),它對應著引用保持有效性的作用域。在大多數時候,生命周期都是隱式且可以被推導出來的,就如同大部分時候類型也是可以被推導的一樣。當出現了多個可能的類型時,就必須手動聲明類型。

(1)、使用生命周期來避免懸垂引用:

生命周期最主要的目標在于避免懸垂引用,進而避免程序引用到非預期的數據。

(2)、借用檢查器:

Rust編譯器擁有一個借用檢查器 (borrow checker),它被用于比較不同的作用域并確定所有借用的合法性。

fn main() {let r;                // ---------+-- 'a//          |{                     //          |let x = 5;        // -+-- 'b  |r = &x;           //  |       |}                     // -+       |//          |println!("r: {}", r); //          |
}                         // ---------+

在編譯過程中,Rust會比較兩段生命周期的大小,并發現r擁有生命周期a,但卻指向了擁有生命周期b的內存。這段程序會由于ba短而被拒絕通過編譯:被引用對象的存在范圍短于引用者。

(3)、生命周期標注語法:

生命周期的標注并不會改變任何引用的生命周期長度。如同使用了泛型參數的函數可以接收任何類型一樣,使用了泛型生命周期的函數也可以接收帶有任何生命周期的引用。在不影響生命周期的前提下,標注本身會被用于描述多個引用生命周期之間的關系。
生命周期的標注使用了一種明顯不同的語法:它們的參數名稱必須以撇號(')開頭,且通常使用全小寫字符。與泛型一樣,它們的名稱通常也會非常簡短。'a被大部分開發者選擇作為默認使用的名稱。我們會將生命周期參數的標注填寫在&引用運算符之后,并通過一個空格符來將標注與引用類型區分開來。
單個生命周期的標注本身并沒有太多意義,標注之所以存在是為了向Rust描述多個泛型生命周期參數之間的關系。

(4)、函數簽名中的生命周期標注:

如同泛型參數一樣,我們同樣需要在函數名與參數列表之間的尖括號內聲明泛型生命周期參數。在這個簽名中我們所表達的意思是:參數與返回值中的所有引用都必須擁有相同的生命周期。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}

這段代碼的函數簽名向Rust表明,函數所獲取的兩個字符串切片參數的存活時間,必須不短于給定的生命周期'a。這個函數簽名同時也意味著,從這個函數返回的字符串切片也可以獲得不短于'a的生命周期。
當我們在函數簽名中指定生命周期參數時,我們并沒有改變任何傳入值或返回值的生命周期。我們只是向借用檢查器指出了一些可以用于檢查非法調用的約束。

(5)、深入理解生命周期:

當函數返回一個引用時,返回類型的生命周期參數必須要與其中一個參數的生命周期參數相匹配。當返回的引用沒有 指向任何參數時,那么它只可能是指向了一個創建于函數內部的值,由于這個值會因為函數的結束而離開作用域,所以返回的內容也就變成了懸垂引用。

(6)、結構體定義中的生命周期標注:

struct ImportantExcerpt<'a> {part: &'a str,
}
fn main() {let novel = String::from("Call me Ishmael. Some years ago...");let first_sentence = novel.split('.').next().expect("Could not find a '.'");let i = ImportantExcerpt { part: first_sentence };
}

(7)、方法定義中的生命周期標注:

結構體字段中的生命周期名字總是需要被聲明在impl關鍵字之后,并被用于結構體名稱之后,因為這些生命周期是結構體類型的一部分。
在impl代碼塊的方法簽名中,引用可能是獨立的,也可能會與結構體字段中的引用的生命周期相關聯。另外,生命周期省略規則在大部分情況下都可以幫我們免去方法簽名中的生命周期標注。
我們定義一個名為level的方法,它僅有一個指向self的參數,并返回i32類型的值作為結果,這個結果并不會引用任何東西:

impl<'a> ImportantExcerpt<'a> {fn level(&self) -> i32 {3}
}

(8)、靜態生命周期:

Rust中還存在一種特殊的生命周期’static,它表示整個程序的執行期。所有的字符串字面量都擁有’static生命周期,示例:

let s: &'static str = "I have a static lifetime.";

字符串的文本被直接存儲在二進制程序中,并總是可用的。因此,所有字符串字面量的生命周期都是’static。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/42382.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/42382.shtml
英文地址,請注明出處:http://en.pswp.cn/news/42382.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TDD(測試驅動開發)?

01、前言 很早之前&#xff0c;曾在網絡上見到過 TDD 這 3 個大寫的英文字母&#xff0c;它是 Test Driven Development 這三個單詞的縮寫&#xff0c;也就是“測試驅動開發”的意思——聽起來很不錯的一種理念。 其理念主要是確保兩件事&#xff1a; 確保所有的需求都能被照…

macOS Ventura 13.5.1(22G90)發布(附黑/白蘋果系統鏡像地址)

系統鏡像下載&#xff1a;百度&#xff1a;黑果魏叔 系統介紹 黑果魏叔 8 月 18 日消息&#xff0c;蘋果今日向 Mac 電腦用戶推送了 macOS 13.5.1 更新&#xff08;內部版本號&#xff1a;22G90&#xff09;&#xff0c;本次更新距離上次發布隔了 24 天。 本次更新重點修復了…

Redis 緩存過期及刪除

一、Redis緩存過期策略 物理內存達到上限后&#xff0c;像磁盤空間申請虛擬內存(硬盤與內存的swap),甚至崩潰。 內存與硬盤交換 (swap) 虛擬內存&#xff0c;頻繁I0 性能急劇下降&#xff0c;會造成redis內存急劇下降&#xff1b; 一般設置物理內存的3/4&#xff0c;在redis…

內存不足V4L2 申請DMC緩存報錯問題

當內存不足時,V4L2可能存在申請DMA緩存報錯,如下日志: 13:36:54:125 [15070.640862] rkcifhw fdfe0000.rkcif: swiotlb buffer is full (sz: 1843200 bytes) 13:36:54:125 [15070.640891] rkcifhw fdfe0000.rkcif: swiotlb: coherent allocation failed, size=1843200 13:3…

超分辨率地震速度模型

文獻分享 1. Multitask Learning for Super-Resolution 原題目&#xff1a;Multitask Learning for Super-Resolution of Seismic Velocity Model 全波形反演&#xff08;FWI&#xff09;是估算地下速度模型的強大工具。與傳統反演策略相比&#xff0c;FWI充分利用了地震波的…

typedef

t y p e d e f typedef typedef 聲明&#xff0c;簡稱typedef&#xff0c;是創建現有類型的新名字。 比如&#xff1a; #include <bits/stdc.h> using namespace std; typedef long long ll; int main() {ll n;scanf("%lld",&n);printf("%lld"…

C++ 面向對象三大特性——多態

?<1>主頁&#xff1a;我的代碼愛吃辣 &#x1f4c3;<2>知識講解&#xff1a;C 繼承 ??<3>開發環境&#xff1a;Visual Studio 2022 &#x1f4ac;<4>前言&#xff1a;面向對象三大特性的&#xff0c;封裝&#xff0c;繼承&#xff0c;多態&#xff…

30W IP網絡有源音箱 校園廣播音箱

SV-7042XT是深圳銳科達電子有限公司的一款2.0聲道壁掛式網絡有源音箱&#xff0c;具有10/100M以太網接口&#xff0c;可將網絡音源通過自帶的功放和喇叭輸出播放&#xff0c;可達到功率30W。同時它可以外接一個30W的無源副音箱&#xff0c;用在面積較大的場所。5寸進口全頻低音…

RNN模型簡單理解和CNN區別

目錄 神經網絡&#xff1a;水平方向延伸&#xff0c;數據不具有關聯性 ? RNN&#xff1a;在神經網絡的基礎上加上了時間順序&#xff0c;語義理解 ?RNN: 訓練中采用梯度下降&#xff0c;反向傳播 ? 長短期記憶模型 ?輸出關系&#xff1a;1 toN&#xff0c;N to N 單入…

Spring三級緩存

目錄 循環依賴問題 三級緩存 三級緩存創建Bean的流程&#xff08;解決循環依賴問題&#xff09; 三級緩存的局限性 Spring的三級緩存是為了解決單例Bean的循環依賴問題而存在的。 循環依賴問題 簡單來說就是A依賴B&#xff0c;而B又依賴A。即創建A的時候&#xff0c;需要先…

【HarmonyOS】【DevEco Studio】ohpm安裝失敗該如何解決?

【關鍵詞】 HarmonyOS、DevEco Studio、ohpm安裝失敗 【問題背景及解決方案】 最近遇到很多DevEco Studio安裝ohpm失敗的問題&#xff0c;下面給大家介紹幾種出現的問題以及解決方案&#xff1a; 1、ohpm not set up&#xff0c;報錯截圖如下&#xff1a; ? 解決方案&…

一百六十、Kettle——Linux上安裝的Kettle9.2.0連接Hive3.1.2

一、目標 Kettle9.2.0在Linux上安裝好后&#xff0c;需要與Hive3.1.2數據庫建立連接 之前已經在本地上用kettle9.2.0連上Hive3.1.2 二、各工具版本 &#xff08;一&#xff09;kettle9.2.0 kettle9.2.0安裝包網盤鏈接 鏈接&#xff1a;https://pan.baidu.com/s/15Zq9w…

C++中class嵌套時構造函數,析構函數調用的順序

#include<iostream> using namespace std; class Phone { public:Phone(string pname){m_pnamepname;cout<<"phone的構造函數調用"<<endl;}~Phone(){cout<<"Phone的析構函數調用"<<endl;}string m_pname; }; class Person {…

網安周報|Monti Ransomware團伙推出了一個新的Linux加密器

Monti Ransomware團伙推出了一個新的Linux加密器 經過兩個月的休息&#xff0c;Monti 勒索軟件運營商帶著新的 Linux 版本的加密器返回。該變體被用于針對政府和法律部門組織的攻擊。研究人員注意到兩個團伙的TTP之間有多個相似之處&#xff0c;Monti運營商還基于Conti泄露的源…

2023 Robocom 游記+題解

Robocom賽前一天熬夜打了一場edu,全程瞇瞇眼&#xff0c;三題滾粗了&#xff0c;前三題花了一小時才寫完&#xff0c;第四題寫了一小時也沒寫明白&#xff0c;好像預示著Robocom的結局&#xff1f; 早上七點醒了&#xff0c;感覺自己渾身無力&#xff0c;想睡覺但是又睡不著的…

AutoSAR配置與實踐(基礎篇)3.3 BSW的通信功能

傳送門 -> AUTOSAR配置與實踐總目錄 AutoSAR配置與實踐&#xff08;基礎篇&#xff09;3.3 BSW的通信功能 一、收發過程概覽1.1 發送過程概覽1.2 接收過程概覽 二、BSW的通信功能模塊組成三、收發過程解析3.1 發送過程3.2 發送后的結果確認3.3 接收過程 一、收發過程概覽 1…

Airbnb開源數據可視化工具Visx

一、什么是visx visx 是用于 React 的富有表現力的底層可視化組件集合,結合了 d3 的強大功能來生成可視化,以及 React 更新 DOM 的諸多優勢。 在 Airbnb 內部,visx 的目標是統一整個公司的可視化堆棧,在此過程中,創建了 visx 項目,從而有效的將 D3 的強大功能與 React …

內核調試之devmem直接讀寫寄存器

今天分享一個內核調試實用工具——devmem。 相信很多做底層驅動的人都會經常用到。 什么是devmem&#xff1f; 在Linux系統&#xff0c;如果我們想要訪問某個寄存器&#xff0c;就需要寫一個驅動程序&#xff0c;在驅動中映射寄存器地址&#xff0c;轉為虛擬地址后就可以訪問…

windows電腦系統自帶的畫圖工具如何實現自由拼圖

1.首先選中你要拼接的第一張圖片&#xff0c;右鍵選著編輯&#xff0c;會自動打開自帶的畫圖工具 然后就是打開第一張圖片&#xff0c;如下圖所示 接著就是將畫布托大&#xff0c;如下圖所示。 然后點擊選擇&#xff0c;選擇下面的空白區域&#xff0c;選著區域的范圍要比準備拼…

05-微信小程序常用組件-表單組件

05-微信小程序常用組件-表單組件 文章目錄 表單組件button 按鈕案例代碼 form 表單案例代碼 image 圖片支持長按識別的碼案例代碼 微信小程序包含了六大組件&#xff1a; 視圖容器、 基礎內容、 導航、 表單、 互動和 導航。這些組件可以通過WXML和WXSS進行布局和樣式設…