懂你,更懂Rust系列之集合、泛型、trait(接口)、生命周期和錯(cuò)誤處理
總在剎那間有一些了解
說(shuō)過(guò)的話不可能會(huì)實(shí)現(xiàn)
就在一轉(zhuǎn)眼 發(fā)現(xiàn)你的臉
已經(jīng)陌生 不會(huì)再像從前
請(qǐng)注意,本篇文章有介紹的有一些Rust非初級(jí)理論(比如trait、生命周期和引用有效性)的東西了,應(yīng)該算中級(jí)一點(diǎn)了吧,所以篇幅較長(zhǎng),請(qǐng)花點(diǎn)耐心咯。最重要一點(diǎn):代碼需要多手動(dòng)敲!敲得越多理解越快。
后續(xù)因?yàn)闀?huì)有幾個(gè)Rust的常見(jiàn)API介紹,所以,這里建個(gè)rust_api包。

一、泛型
首先思考一個(gè)場(chǎng)景,假設(shè)需要從一個(gè)i8數(shù)據(jù)類型的數(shù)組中,找到最大的那個(gè)數(shù)字
很簡(jiǎn)單,先定義一個(gè)函數(shù),get_max_number,然后方法傳入?yún)?shù)為數(shù)組引用,循環(huán)比較,找到其最大值,然后調(diào)用運(yùn)行:
fn?main()?{println!("Hello, lgli!");let array:[i8;6] = [12,3,5,2,7,9];let result: i8 = get_max_number(&array);println!("數(shù)組中的最大值是:{}",result);}//找到最大值fn?get_max_number(array?:?&[i8])->i8{let mut result : i8 = array[0];????for?&x?in?array?{if x > result{result = x;}}result}
上述函數(shù)函數(shù)體的代碼,這里就不在詳述了,簡(jiǎn)單的基礎(chǔ)語(yǔ)法,比如引用、可變變量就不在多說(shuō)了,前面已經(jīng)說(shuō)了很多了。

那么接下來(lái)的是,如果,數(shù)組包含的是i16的數(shù)字呢?
很明顯,需要再寫(xiě)一個(gè)和上述get_max_number相同的方法,除了參數(shù)類型不一樣之外。這樣子的大量代碼重復(fù)量,肯定是不優(yōu)雅的,所以這里Rust提供了一種優(yōu)雅的解決方案,那就是泛型<Generic>
比如上述函數(shù)可以修改為:
fn main() {println!("Hello, lgli!");let array:[i16;6] = [1296,3,5,2,7,9];let result: i16 = get_max_number(&array);println!("數(shù)組中的最大值是:{}",result);}//找到最大值fn get_max_number<T: PartialOrd + Copy>(array : &[T])->T{let mut result : T = array[0];for &x in array {if x > result{result = x;}}result}
注意上述代碼,函數(shù)get_max_number返回值變成了T
同時(shí)在函數(shù)前面加上了<T: PartialOrd + Copy>
然后參數(shù):array : &[T]
這里的T表示泛型,即方法可以傳入任意類型的數(shù)組,可以是i8的,也可以是i16的,甚至還可以是char
這里是定義了一個(gè)帶返回值的泛型函數(shù),返回值和參數(shù)傳入的類型一致,同時(shí)函數(shù)名后面需要跟上泛型類型,也就是這里的<T>,本函數(shù)因?yàn)樯婕耙恍┢渌鸕ust的理論,比如添加std::cmp::PartialOrd是為了使函數(shù)體的比較大小x>result可以實(shí)現(xiàn)比較,添加std::cmp::Copy是為了保證result的初始化,意思是,只對(duì)在棧中變量初始化,因?yàn)檫@里泛型會(huì)考慮到是否有堆中數(shù)據(jù),堆中數(shù)據(jù),則需要加引用&,但是又不能保證都是堆中數(shù)據(jù),所以這里這么做。先做個(gè)簡(jiǎn)單的了解,后續(xù)會(huì)詳細(xì)說(shuō)到!
所以這個(gè)時(shí)候,函數(shù)get_max_number就可以是各種類型的了,不需要copy各種大量重復(fù)代碼了:

同樣的,可以在結(jié)構(gòu)體中定義泛型:
//定義了課程,課程名字是字符串,課程內(nèi)容是個(gè)泛型struct Course<T>{name:String,content:T}
還有在方法體中定義泛型:
//方法定義中的泛型impl <T> Course <T> {fn say_course_name(&self){println!("lgli的課程名字是:{}",&self.name);}????//方法中函數(shù)泛型fn get_content(&self) -> &T {&self.content}}
這里提一點(diǎn)前面的基礎(chǔ),在Rust中不要把方法和函數(shù)混為一談了。
然后測(cè)試下上述代碼:
fn?main()?{let my_course = Course{name:String::from("體育"),content : "踢足球"};my_course.say_course_name();let my_course_content = my_course.get_content();print!("lgli的課程內(nèi)容是:{:#}",my_course_content);}//定義了課程,課程名字是字符串,課程內(nèi)容是個(gè)泛型struct Course<T>{name:String,content:T}//方法定義中的泛型impl <T> Course <T> {fn say_course_name(&self){println!("lgli的課程名字是:{}",&self.name);}????//方法中函數(shù)泛型fn get_content(&self) -> &T {&self.content}}
編譯運(yùn)行

看下面這個(gè)泛型例子:
fn?main()?{let point_one : Point<i8,char> = Point{x:8,y:'c'????};let point_two : Point<i16,String> = Point{x:8,y:String :: from("lgli")????};????let?point_three?:?Point<i8,String>?=?point_one.update_y_value(point_two);print!("交換Y值的結(jié)構(gòu)為:{:?}",point_three);}//定義泛型結(jié)構(gòu)體#[derive(Debug)]struct Point<U,V>{x : U,y : V}//結(jié)構(gòu)體方法impl <U,V> Point<U,V>{//方法函數(shù)fn update_y_value<W,Z>(self,other_point : Point<W,Z>) ->Point<U,Z>{Point{x : self.x,y : other_point.y}}}
編譯運(yùn)行:

前面已經(jīng)有介紹過(guò)Rust標(biāo)準(zhǔn)庫(kù)中的一些泛型實(shí)例,比如Option<T>,這也都是結(jié)構(gòu)體泛型,
在Rust中,泛型編譯通過(guò)泛型編碼的單態(tài)化(monomorphization)來(lái)保證效率,單態(tài)化是一個(gè)通過(guò)填充編譯時(shí)使用的具體類型,將通用代碼轉(zhuǎn)換為特定代碼的過(guò)程
比如:
let integer = Some(5);let float = Some(5.0);
當(dāng) Rust 編譯這些代碼的時(shí)候,它會(huì)進(jìn)行?單態(tài)化。編譯器會(huì)讀取傳遞給
?Option<T>?
的值并發(fā)現(xiàn)有兩種?Option<T>:
一個(gè)對(duì)應(yīng)?i32?另一個(gè)對(duì)應(yīng)?f64。
為此,它會(huì)將泛型定義?Option<T>
展開(kāi)為?Option_i32?和?Option_f64,接著將泛型定義替換為這兩個(gè)具體的定義。
即:
enum Option_i32 {Some(i32),None,}enum Option_f64 {Some(f64),None,}fn main() {let integer = Option_i32::Some(5);let float = Option_f64::Some(5.0);}
二、集合
在rust_api中新建一個(gè)二進(jìn)制的crate-->vec

首先要介紹的第一個(gè)集合是?Vec<T>
let v: Vec<i32> = Vec::new();上述代碼,初始化一個(gè)i32數(shù)據(jù)類型的集合
可以使用push添加集合數(shù)據(jù),但是必須都是i32的:
fn main() {println!("Hello, lgli!");let mut v: Vec<i32> = Vec::new();v.push(32);v.push(64);????v.push(128);??? print!("集合的長(zhǎng)度為:{}",v.len());}
當(dāng)存在集合元素被引用時(shí)候,嘗試改變集合數(shù)據(jù)是不行的:
fn main() {println!("Hello, lgli!");let mut v: Vec<i32> = Vec::new();v.push(32);v.push(64);v.push(128);let o = &v[1];v.push(39);println!("集合第3個(gè)元素是:{}",o);}
上述代碼,變量o獲取了集合元素引用,同時(shí)borrow到了println!
這時(shí)候,嘗試push元素是會(huì)報(bào)錯(cuò)的
所以編譯報(bào)錯(cuò):

修改上述代碼:
fn main() {println!("Hello, lgli!");let mut v: Vec<i32> = Vec::new();v.push(32);v.push(64);v.push(128);let &o = &v[1];v.push(39);println!("集合第3個(gè)元素是:{}",o);}
編譯運(yùn)行:

遍歷集合元素可以使用for循環(huán):
let v: Vec<i32> = vec![13,25,159,1336];for &i in &v{println!("{}",i);}
編譯運(yùn)行:

也可以嘗試對(duì)集合的元素做運(yùn)算:
let mut v: Vec<i32> = vec![13,25,159,1336];for i in &mut v{*i += 22;}for &i in &v{println!("{}",i);}
編譯運(yùn)行:

這里通過(guò)對(duì)元素都加了22,從而改變了元素的值
這里需要先記住一個(gè)點(diǎn):在使用?+=?運(yùn)算符之前必須使用解引用運(yùn)算符(*)獲取?i?中的值。
這個(gè)解引用運(yùn)算符追蹤指針的值中會(huì)介紹到
集合vec!只能存放相同類型的數(shù)據(jù),這很不方便,在某些特殊情況下,需要保存不同類型的數(shù)據(jù),這就不適用了
這時(shí)候,可以這么做,定義枚舉,成員變量是不同類型,這時(shí)候,集合元素為枚舉,這就可以滿足上述需求了,比如:
#[derive(Debug)]enum Course {Text(String),Int(i128),Float(f64)}let some_vec = vec![Course ::Int(160),Course :: Float(0.265),Course :: Text(String :: from("lgli"))];for x in &some_vec {println!("{:?}",x);}
編譯運(yùn)行:

三、字符串
新建一個(gè)str二進(jìn)制庫(kù):

字符串其實(shí)是一個(gè)非常復(fù)雜的玩意兒了
在前面的例子中多次都是用到過(guò),但是真正細(xì)細(xì)分析,還沒(méi)有弄過(guò),今天就來(lái)分析這個(gè)復(fù)雜的字符串
首先,Rust 的核心語(yǔ)言中只有一種字符串類型:str,它通常以被借用的形式出現(xiàn),&str,前面說(shuō)到的字符串 slice:它們是一些儲(chǔ)存在別處的 UTF-8 編碼字符串?dāng)?shù)據(jù)的引用。比如字符串字面值被儲(chǔ)存在程序的二進(jìn)制輸出中,字符串 slice 也是如此。
稱作?String?的類型是由標(biāo)準(zhǔn)庫(kù)提供的,而沒(méi)有寫(xiě)進(jìn)核心語(yǔ)言部分,它是可增長(zhǎng)的、可變的、有所有權(quán)的、UTF-8 編碼的字符串類型。
Rust 標(biāo)準(zhǔn)庫(kù)中還包含一系列其他字符串類型,比如?OsString、OsStr、CString?和?CStr
這些字符串類型能夠以不同的編碼,或者內(nèi)存表現(xiàn)形式上以不同的形式,來(lái)存儲(chǔ)文本內(nèi)容
很多?Vec?可用的操作在?String?中同樣可用,從以?new?函數(shù)創(chuàng)建字符串開(kāi)始
let mut s = String::new();也可以通過(guò)push添加數(shù)據(jù)
let mut s = String::new();s.push('l');s.push('g');s.push('l');s.push('i');println!("{}",s);
編譯運(yùn)行

也可以直接,將str轉(zhuǎn)化為String
let mut s = "你好".to_string();s.push('l');println!("{}",s);
或者
let mut s = String :: from("你好");s.push('l');println!("{}",s);
這里添加的str,是逐個(gè)char添加,可以使用push_str添加slice片
let mut s = String :: from("你好");s.push_str("lgli");println!("{}",s);
也可以這樣
let mut s = String :: from("你好");let sts = "lgli";s.push_str(sts);println!("{}",s);
這里需要記住一點(diǎn),push_str是不糊獲取參數(shù)的所有權(quán)的,所有在執(zhí)行上述代碼之后,sts依然有效
下面代碼可以說(shuō)明這一點(diǎn):
let mut s = String :: from("你好");let sts = String :: from("lgli");s.push_str(&sts);println!("{}",s);
不能直接傳入sts,因?yàn)橹苯觽魅?,則說(shuō)明方法要獲取所有權(quán),但是這里只能傳入其引用
看下面代碼
let s1 = String :: from("hello, ");let s2 = String :: from("lgli!");let s3 = s1 + s2;println!("{}",s3);
如果從其他語(yǔ)言的角度,比如Java,這個(gè)代碼是沒(méi)有什么問(wèn)題的,但是在Rust中,這里是有問(wèn)題的
首先Rust中的字符串的+號(hào),會(huì)調(diào)用string的add方法
#[stable(feature = "rust1", since = "1.0.0")]impl Add<&str> for String {type Output = String;#[inline]fn add(mut self, other: &str) -> String {self.push_str(other);self}}
上述是Rust的add源碼,這里可以看到,參數(shù)傳入的是一個(gè)&str,即str引用,所以上述代碼應(yīng)該改成:
let s1 = String :: from("hello, ");let s2 = String :: from("lgli!");let s3 = s1 + &s2;println!("{}",s3);
那么這里應(yīng)該又有問(wèn)題了,這個(gè)地方的&s2是一個(gè)&String也不是&str呀?
Rust 使用了一個(gè)被稱為?解引用強(qiáng)制多態(tài)(deref coercion)的技術(shù),可以理解為,它把?&s2?變成了?&s2[..],這里不做過(guò)多的擴(kuò)展。
在需要使用多個(gè)+拼接字符串的時(shí)候,這個(gè)就顯得很尷尬了,Rust提供了一種
format!
let s1 = String::from("one");let s2 = String::from("two");let s3 = String::from("three");let s = format!("{}-{}-{}", s1, s2, s3);
format!不會(huì)發(fā)生移動(dòng),即在上述代碼運(yùn)行之后,s1、s2、s3都是有效的
但是使用+,則首個(gè)字符串是會(huì)發(fā)生移動(dòng)的

但是后者不會(huì)

String其內(nèi)部也是一個(gè)集合vec

下面嘗試下標(biāo)的方式獲取字符串

很遺憾,報(bào)錯(cuò)了
為什么?
前面我們看到了,String,內(nèi)部其實(shí)是一個(gè)u8類型的集合,即所有的字符串都是通過(guò)UTF-8 編碼成u8保存在集合中,索引訪問(wèn)是一個(gè)確切的位置,但是一個(gè)字符串字節(jié)值的索引并不總是對(duì)應(yīng)一個(gè)有效的 Unicode 標(biāo)量值。

即此時(shí)s1[0]是需要返回228?還是'你'?
這是其中一個(gè)無(wú)法通過(guò)索引訪問(wèn)string的原因
還有一個(gè)Rust 不允許使用索引獲取 String 字符的原因是,索引操作預(yù)期總是需要常數(shù)時(shí)間 (O(1))。但是對(duì)于 String 不可能保證這樣的性能,因?yàn)?Rust 必須從開(kāi)頭到索引位置遍歷來(lái)確定有多少有效的字符。
四、哈希map
新建一個(gè)map二進(jìn)制包

哈希map是通過(guò)鍵值對(duì)<key,value>來(lái)保存任意數(shù)據(jù)類型的數(shù)據(jù)集合,查找數(shù)據(jù)也是直接通過(guò)key來(lái)尋找對(duì)應(yīng)的value,而不需要像集合那樣通過(guò)遍歷索引
下面嘗試新建一個(gè)哈希map,并插入值
let mut map : HashMap<String,i8> = HashMap::new();map.insert(String::from("one"), 1);map.insert(String::from("two"), 2);
這里新建了一個(gè)map,其中鍵值對(duì)類型分別為String和i8
使用insert可以給map中添加對(duì)應(yīng)的key及其對(duì)應(yīng)的value
let mut map : HashMap<String,i8> = HashMap::new();map.insert(String::from("one"), 1);map.insert(String::from("two"), 2);map.insert(String::from("two"), 3);let s = map.get("two");println!("{:?}",s.unwrap());
重復(fù)insert的key會(huì)覆蓋掉之前的value值,獲取key對(duì)應(yīng)的value,使用get
返回值是Option枚舉
上述代碼運(yùn)行結(jié)果:

插入堆上數(shù)據(jù)類型,將會(huì)獲取數(shù)據(jù)所有權(quán)
let map_key = String::from("lgli");let mut map : HashMap<String,i8> = HashMap :: new();map.insert(map_key,1);println!("map_key 不在有效,這里使用會(huì)報(bào)錯(cuò):{}",map_key);
上述代碼編譯報(bào)錯(cuò):

有的時(shí)候,可能未知或者忘了,map中是否有某個(gè)key的value值,這時(shí)候,有一個(gè)想法就是,想要檢驗(yàn),如果map中存在某個(gè)key的value值,就不變,否則就插入,而不是直接覆蓋掉。Rust提供了這種API
let mut map : HashMap<String,i8> = HashMap::new();map.insert("lgli".to_string(),1);//如果有l(wèi)gli的key,則不做任何事,否則插入value為2map.entry(String :: from("lgli")).or_insert(2);//這里lgli的value值為1let value : &i8 = map.get("lgli").unwrap();println!("lgli對(duì)應(yīng)的value值為:{}",value);
上述代碼運(yùn)行結(jié)果:

map.entry(&str).or_insert(value)
or_insert?方法會(huì)返回這個(gè)鍵的值的一個(gè)可變引用
根據(jù)此特性,可以進(jìn)行一個(gè)計(jì)數(shù)操作
比如計(jì)算字符串內(nèi)相同的slice的次數(shù):
let text = "你好 歡迎 來(lái)到 我的 世界 你好 我的 世界 很 歡迎 你";let mut map : HashMap<&str,i8> = HashMap::new();for word in text.split_whitespace(){let x = map.entry(word).or_insert(0);*x += 1;}println!("{:?}",map);
運(yùn)行上述代碼,得到text的相同字符串的次數(shù)組成的map

有點(diǎn)累了,再來(lái)首音樂(lè):
五、trait:定義共享的行為
還記得上面泛型開(kāi)篇的那個(gè)找最大數(shù)字的方法么?get_max_number
就是這個(gè):
fn get_max_number<T: std::cmp::PartialOrd + Copy>(array : &[T])->T{let mut result : T = array[0];for &x in array {if x > result{result = x;}}result}
前面就直接說(shuō)了為啥,但是沒(méi)有這個(gè)過(guò)程的來(lái)源。這里就來(lái)看看這個(gè),先去掉這個(gè)std::cmp::Copy運(yùn)行看看

首先這里IDE都已經(jīng)把報(bào)錯(cuò)了,也就談不上編譯了。
看看這個(gè)錯(cuò)誤信息
move occurs because `array[_]` has type `T`, which does not implement the `Copy` trait
Rust說(shuō),這個(gè)T是沒(méi)有實(shí)現(xiàn)這個(gè)'Copy' trait
然后加上這個(gè)std::cmp::Copy這就好了,看下這個(gè)std::cmp::Copy源碼

這里的是一個(gè)trait關(guān)鍵字定義的,非前面說(shuō)到的結(jié)構(gòu)體、枚舉、方法、函數(shù)等任意一種定義形式
然后這個(gè)std::cmp::Copy還實(shí)現(xiàn)了std::cmp::Clone
同樣的std::cmp::Clone也是一個(gè)trait
那么,下面先了解下這個(gè)trait
一個(gè)類型的行為由其可供調(diào)用的方法構(gòu)成。如果可以對(duì)不同類型調(diào)用相同的方法的話,這些類型就可以共享相同的行為了。trait 定義是一種將方法簽名組合起來(lái)的方法,目的是定義一個(gè)實(shí)現(xiàn)某些目的所必需的行為的集合。
從上面這段話可以看出,trait定義了相同行為的方法簽名。
新建包my_trait
文件lib.rs
例如下面的定義trait:
//定義traitpub trait Summary{fn summary(&self)->String;}
這里使用?trait?關(guān)鍵字來(lái)聲明一個(gè) trait,后面是 trait 的名字,在上面例子中是?Summary。在大括號(hào)中聲明描述實(shí)現(xiàn)這個(gè) trait 的類型所需要的行為的方法簽名,在這個(gè)例子中是?fn summarize(&self) -> String。
trait 體中可以有多個(gè)方法:一行一個(gè)方法簽名且都以分號(hào)結(jié)尾。
這點(diǎn)和Java的接口類似。
但是,Rust的trait的方法可以有默認(rèn)實(shí)現(xiàn),比如<lib.rs>:
//定義traitpub trait Summary{//只定義方法簽名fn summary(&self)->String;//有默認(rèn)實(shí)現(xiàn)的trait方法fn summary_other()->String{String :: from("hello lgli")}//只定義方法簽名fn summary_other_for(&self)->String;}
上面有一個(gè)方法有默認(rèn)的實(shí)現(xiàn)
下面定義結(jié)構(gòu)體,同時(shí)定義實(shí)現(xiàn)這個(gè)trait的方法函數(shù)
//定義traitpub trait Summary{//只定義方法簽名fn summary(&self)->String;//有默認(rèn)實(shí)現(xiàn)的trait方法fn summary_other()->String{String :: from("hello lgli")}//只定義方法簽名fn summary_other_for(&self)->String;}#[derive(Debug)]pub struct Life{pub year : String,pub is_happiness : String}//沒(méi)有實(shí)現(xiàn)默認(rèn)trait方法impl Summary for Life{fn summary(&self) -> String {format!("Life has been more than {} years, your life is happy? {}",self.year, self.is_happiness)}fn summary_other_for(&self) -> String {format!("Having lived for more than {} years , \your life is seriously whether someone else is happy or not ? {}",self.year, self.is_happiness)}}#[derive(Debug)]pub struct Study{pub name : String,pub content : String}//實(shí)現(xiàn)所有trait方法impl Summary for Study{fn summary(&self) -> String {format!("I learned the course of {}, \the main content of which was the {}",self.name, self.content)}fn summary_other() -> String {String :: from("hello lgli, the best for you happiness")}fn summary_other_for(&self) -> String {format!("I learned the course of {}, and at the same time, \I also brought some content about {} to others",self.name, self.content)}}
在實(shí)現(xiàn)trait方法的時(shí)候,trait已經(jīng)有默認(rèn)實(shí)現(xiàn)的方法,就可以不用實(shí)現(xiàn)了<比如Life>,當(dāng)然也可以實(shí)現(xiàn)<比如Study>
調(diào)用測(cè)試,main.rs:
use my_trait;use my_trait::Summary;fn main() {let life = my_trait :: Life{year: "18".to_string(),is_happiness: "yes".to_string()};let string = Summary::summary(&life);println!("trait原生調(diào)用沒(méi)有實(shí)現(xiàn)方法的trait:");println!("{}",string);println!("trait原生調(diào)用有默認(rèn)實(shí)現(xiàn)方法的trait:");let string = Summary::summary_other(&life);println!("{}",string);println!("trait原生調(diào)用沒(méi)有實(shí)現(xiàn)方法的trait:");let string = Summary::summary_other_for(&life);println!("{}",string);println!("Life結(jié)構(gòu)體調(diào)用trait方法:");let my_life = life.summary();println!("{}",my_life);let my_life_for = life.summary_other_for();println!("{}",my_life_for);let study = my_trait :: Study{name: "history".to_string(),content: "the modern and contemporary history of China".to_string()};println!("Study結(jié)構(gòu)體調(diào)用trait方法:");let my_study = study.summary();println!("{}",my_study);let my_study_other = study.summary_other();println!("{}",my_study_other);let string = Summary::summary_other(&study);println!("{}",string);let my_study_for = study.summary_other_for();println!("{}",my_study_for);}
當(dāng)直接通過(guò)trait調(diào)用trait方法簽名時(shí),需要傳入有實(shí)現(xiàn)該方法的結(jié)構(gòu)體
調(diào)用trait結(jié)構(gòu)體有默認(rèn)的實(shí)現(xiàn)方法時(shí),如果結(jié)構(gòu)體本身沒(méi)有實(shí)現(xiàn),則調(diào)用trait默認(rèn)實(shí)現(xiàn)
上述測(cè)試,編譯運(yùn)行:

結(jié)構(gòu)印證了前面描述的話
除此之外,trait方法,還可以作為參數(shù)傳入函數(shù)體中,比如下列代碼,lib.rs ?:
//trait作為參數(shù)傳入函數(shù)pub fn trait_param(trait_obj : &impl Summary)->String{trait_obj.summary_other_for()}
測(cè)試下 main.rs
use?my_trait;fn?main()?{let study = my_trait :: Study{name: "history".to_string(),content: "the modern and contemporary history of China".to_string()????};let trait_param = my_trait::trait_param(&study);????println!("{}",trait_param);}
編譯運(yùn)行:

trait作為參數(shù)傳入函數(shù)還有另外的寫(xiě)法,比如:
pub fn trait_param_other<T : Summary>(trait_obj : &T)->String{trait_obj.summary()}
上述寫(xiě)法,是不是特別的眼熟了,回頭去想想找最大數(shù)字的函數(shù)
get_max_number
傳入了一個(gè)泛型T 同時(shí)指定了泛型T必須實(shí)現(xiàn)Copy
為什么必須實(shí)現(xiàn)Copy?
還記得函數(shù)中有這么一句代碼:
let mut result : i8 = array[0];
這里,如果array[0]是堆中數(shù)據(jù)或棧中數(shù)據(jù),這里是不一樣的概念,如果是堆中數(shù)據(jù),這里就會(huì)發(fā)生移動(dòng),之后不在有效,但是后面是有效的,所以,如果是堆中數(shù)據(jù),這里需要加引用&,而代碼并沒(méi)有加,所以必須指明T的類型了,必須實(shí)現(xiàn)了Copy的類型,才可以實(shí)現(xiàn)沒(méi)有移動(dòng)的賦值。類似的PartialOrd為了比較大小,前面也提到了,也是相同的道理,不是所有類型都可以通過(guò)>符號(hào)來(lái)比較大小的。
六、生命周期和引用有效性
之前的Rust介紹了引用,同時(shí),今天的前面例子中也有用到引用,比如上面的trait作為參數(shù)傳入的時(shí)候,其實(shí)也只是傳入的引用,那么說(shuō)明引用在Rust中占據(jù)了舉足輕重的作用,所以有必要了解得更多一些。
新建二進(jìn)制crate,reference
看下面代碼:
fn main() {let y;{????????let?x?=?5;y = &x;}println!("{}",y);}
這里首先申明了一個(gè)y,接著在一個(gè)匿名的函數(shù)體內(nèi),對(duì)y賦值為x的引用,最后打印輸出y
上述代碼編譯是會(huì)報(bào)錯(cuò)的。
在匿名函數(shù)體內(nèi),x是有效的,但是離開(kāi)了這個(gè)匿名函數(shù)體,x不在有效,那么x的引用也同樣不在有效,所以編譯不通過(guò):

編譯器說(shuō),租借的值,沒(méi)能活得足夠長(zhǎng)
用下面這個(gè)圖來(lái)表示:

'b:表示x的生命周期時(shí)間
'a:表示y的生命周期時(shí)間
很明顯,y的生命周期大于x的,所以這種情況下,y引用x的值是不會(huì)被Rust認(rèn)同的,因?yàn)橐靡呀?jīng)超出了被引用對(duì)象的作用域。
在看下面這個(gè)例子:
fn main() {let s1 = String::from("lgli");let s2 = "hello";let max_length_str = get_long_str(s1.as_str(),s2);println!("{}",max_length_str);}fn get_long_str(s1 : &str,s2 : &str)->&str{if s1.len() > s2.len() {s1}else if s1.len() < s2.len() {s2}else{"the same length"}}
上述代碼,主要目的是想找到2個(gè)字符串中比較長(zhǎng)的一個(gè),并輸出。
但是很遺憾,也是會(huì)報(bào)錯(cuò)的。為什么?
首先看這個(gè)get_long_str方法,方法傳入了2個(gè)引用,然后返回值也是一個(gè)引用,這里就存在一個(gè)引用值作用域的問(wèn)題,編譯器無(wú)法得知返回值的引用是來(lái)自s1或者s2,又或者兩者都不是<比如兩者相等長(zhǎng)度的時(shí)候,返回的是slice>,這些情況編譯器無(wú)法得知的,這就造成的一個(gè)結(jié)果是,當(dāng)返回這個(gè)引用對(duì)象之后,其作用域應(yīng)該是多大的問(wèn)題?
為了避免可能出現(xiàn)的作用域溢出,即租借的值,可能會(huì)超出其作用域范圍,所以上述代碼會(huì)在編譯期直接編譯不通過(guò)。

然后看這里編譯器提出了一個(gè)解決方法
編譯器說(shuō),將函數(shù)簽名和返回值寫(xiě)成這樣:
fn get_long_str<'a>(s1 : &'a str,s2 : &'a str)->&'a str
這個(gè)地方,就先來(lái)看看這個(gè)'a是個(gè)什么玩意兒?
'a其實(shí)就是一個(gè)生命周期注解語(yǔ)法,通過(guò)這個(gè)生命周期注解語(yǔ)法,告訴編譯器,函數(shù)返回值的生命周期和函數(shù)參數(shù)s1和s2中最小生命周期是一樣的,即返回值生命周期與參數(shù)中生命周期最小的那個(gè)參數(shù)的生命周期等同。
在Rust中,生命周期參數(shù)名稱必須以撇號(hào)(')開(kāi)頭,其名稱通常全是小寫(xiě),類似于泛型其名稱非常短。'a?是大多數(shù)人默認(rèn)使用的名稱。生命周期參數(shù)注解位于引用的?&?之后,并有一個(gè)空格來(lái)將引用類型與生命周期注解分隔開(kāi)。生命周期注解,只是申明變量生命周期,并不會(huì)改變變量的生命周期。
這里有一些例子:我們有一個(gè)沒(méi)有生命周期參數(shù)的?i32?的引用,一個(gè)有叫做?'a?的生命周期參數(shù)的?i32?的引用,和一個(gè)生命周期也是?'a?的?i32?的可變引用:
&i32 // 引用&'a i32 // 帶有顯式生命周期的引用&'a mut i32 // 帶有顯式生命周期的可變引用
這時(shí)候,根據(jù)提示,改造上述代碼:
fn get_long_str<'a>(s1 : &'a str,s2 : &'a str)->&'a str{if s1.len() > s2.len() {s1}else if s1.len() < s2.len() {s2}else{"the same length"}}
編譯運(yùn)行:

仔細(xì)看上述函數(shù)
fn get_long_str<'a>(s1 : &'a str,s2 : &'a str)->&'a str
這個(gè)函數(shù)體內(nèi)因?yàn)榭赡芊祷氐?span style="font-family:'Source Code Pro', Consolas, 'Ubuntu Mono', Menlo, 'DejaVu Sans Mono', monospace, monospace;font-size:14.875px;background-color:rgb(246,247,246);">s1或者s2,所以必須給參數(shù)都加上生命周期注解,那么有沒(méi)有一種情況,指定返回s1,從而達(dá)到不需要給s2加生命周期注解呢?
比如:
fn get_long_str<'a>(s1 : &'a str,s2 : &str)->&'a str{println!("{}",s2);s1}
這里是肯定可以的,因?yàn)榉祷刂荡_定了是s1,所以可以編譯通過(guò)并運(yùn)行
那么同等的道理,這樣子也是可以的:
fn get_long_str<'a>(s1 : &str,s2 : &str)->&'a str{println!("{}",s2);println!("{}",s1);"你好"}
那么這里就有一個(gè)疑問(wèn)了,指定生命周期注解是為了保證作用域的一致性,在上述這種情況下,結(jié)果值沒(méi)有和s1或者s2任何一個(gè)相關(guān)聯(lián),那么是否可以不需要指定生命周期注解呢?

這里是也是編譯不通過(guò)的,但是至于為什么,其實(shí)我確實(shí)是有點(diǎn)懵。
那么下面這樣子呢:
fn get_long_str<'a>(s1 : &str,s2 : &str)->&'a str{println!("{}",s2);println!("{}",s1);let result = String::from("你好");result.as_str()}
上述代碼無(wú)法編譯通過(guò),因?yàn)橐脤?duì)象本身離開(kāi)這個(gè)方法之后就失效了,即Rust會(huì)產(chǎn)生一個(gè)空的引用<懸垂引用>,這肯定也是不行的。這個(gè)時(shí)候,解決的辦法就是返回一個(gè)擁有所有權(quán)的數(shù)據(jù)類型,比如:
fn get_long_str<'a >(s1 : &str,s2 : &str)->&'a str{println!("{}",s2);println!("{}",s1);"你好"}
這里的返回值是一個(gè)擁有所有權(quán)的str,所以離開(kāi)函數(shù)作用域時(shí)不會(huì)被清除掉的,知道其引用對(duì)象離開(kāi)作用域,會(huì)被清除掉,因?yàn)橐脤?duì)象會(huì)獲取"你好"的所有權(quán)。
下面看下生命周期注解在結(jié)構(gòu)體中的應(yīng)用,
之前定義的結(jié)構(gòu)體,屬性對(duì)象都是擁有其所有權(quán)的,比如:
#[derive(Debug)]struct School{name : String,}
這里定義了一個(gè)學(xué)校結(jié)構(gòu)體,擁有了一個(gè)名字name屬性,name屬性的值是一個(gè)String類型,name屬性同時(shí)還擁有了String的所有權(quán)。
學(xué)??赡苓€有分校,所以這里定義一個(gè)分校屬性,這時(shí)候需要思考一個(gè)問(wèn)題了,這個(gè)分校屬性是否也需要分校數(shù)據(jù)類型的所有權(quán)?可以只是一個(gè)引用么?
從內(nèi)存來(lái)看,獲取一個(gè)引用其實(shí)就夠了,不需要在開(kāi)辟一個(gè)內(nèi)存地址來(lái)保存一個(gè)可能和其他地方一樣的數(shù)據(jù),所以考慮這個(gè)屬性是一個(gè)引用:
//定義學(xué)校#[derive(Debug)]struct School{name : String,branch_school : &BranchSchool}//定義分校#[derive(Debug)]struct BranchSchool {name : String}
上述代碼是會(huì)報(bào)錯(cuò)的,因?yàn)檫@里引用就會(huì)存在引用值生命周期的問(wèn)題,即要保證branch_school指向正確的數(shù)據(jù)對(duì)象,必須要保證branch_school的引用具有和結(jié)構(gòu)體School一樣的生命周期,所以應(yīng)該這么來(lái)定義:
//定義學(xué)校#[derive(Debug)]struct School<'a,'b>{name : String,branch_school : &'a BranchSchool,teachers : Vec<Teacher>,teaching_building: &'b TeachingBuilding}//定義分校#[derive(Debug)]struct BranchSchool {name : String}//定義老師#[derive(Debug)]struct Teacher{name : String,age : i8,address : String}//定義一個(gè)教學(xué)樓#[derive(Debug)]struct TeachingBuilding{name : String,address : String}
測(cè)試編譯運(yùn)行:
let teacher = Teacher{name: "lgli".to_string(),age: 18,address: String :: from("here is my home")};let mut teachers: Vec<Teacher> = Vec::new();teachers.push(teacher);let teaching_building = TeachingBuilding{name: "第一教學(xué)樓".to_string(),address: "在心中".to_string()};let branch_school = BranchSchool{name: "第一分校".to_string()};let school = School{name: String :: from("清華大學(xué)"),branch_school: &branch_school,teachers,teaching_building: &teaching_building};println!("{:#?}",school);
編譯運(yùn)行:

結(jié)果體參數(shù)表明,所有引用的對(duì)象,其存活時(shí)間都一定要比結(jié)構(gòu)體大。
比如,如果下面這樣就會(huì)編譯錯(cuò)誤:

很明顯,上述代碼,結(jié)構(gòu)體的作用域大于結(jié)構(gòu)體屬性引用的作用域了,所以編譯報(bào)錯(cuò)!
在這里,了解了一個(gè)重要的信息,所有的引用都有生命周期。
但是有細(xì)心的朋友,會(huì)發(fā)現(xiàn),在前面講Rust的slice的時(shí)候,有寫(xiě)過(guò)一個(gè)函數(shù),返回值和參數(shù)也都是引用,但是沒(méi)有指定生命周期注解:

上述代碼也是可以通過(guò)編譯并運(yùn)行的,這里沒(méi)有指定生命周期。
這個(gè)函數(shù)沒(méi)有生命周期注解卻能編譯是由于一些歷史原因:在早期版本(pre-1.0)的 Rust 中,這的確是不能編譯的。每一個(gè)引用都必須有明確的生命周期。那時(shí)的函數(shù)簽名將會(huì)寫(xiě)成這樣:
fn?look_for_string<'a>(_s:?&'a?str)?->?&'a?str?
在編寫(xiě)了很多 Rust 代碼后,Rust 團(tuán)隊(duì)發(fā)現(xiàn)在特定情況下 Rust 程序員們總是重復(fù)地編寫(xiě)一模一樣的生命周期注解。這些場(chǎng)景是可預(yù)測(cè)的并且遵循幾個(gè)明確的模式。接著 Rust 團(tuán)隊(duì)就把這些模式編碼進(jìn)了 Rust 編譯器中,如此借用檢查器在這些情況下就能推斷出生命周期而不再?gòu)?qiáng)制程序員顯式的增加注解。這些模式被稱為生命周期省略規(guī)則(lifetime elision rules)。這并不是需要我們遵守的規(guī)則;這些規(guī)則是一系列特定的場(chǎng)景,此時(shí)編譯器會(huì)考慮,如果代碼符合這些場(chǎng)景,就無(wú)需明確指定生命周期。
函數(shù)或方法的參數(shù)的生命周期被稱為?輸入生命周期(input lifetimes),而返回值的生命周期被稱為?輸出生命周期(output lifetimes)。
Rust編譯器采用三條規(guī)則來(lái)判斷引用何時(shí)不需要明確的注解:
1、每一個(gè)引用的參數(shù)都有它自己的生命周期參數(shù)。換句話說(shuō)就是,有一個(gè)引用參數(shù)的函數(shù)有一個(gè)生命周期參數(shù):fn foo<'a>(x: &'a i32),有兩個(gè)引用參數(shù)的函數(shù)有兩個(gè)不同的生命周期參數(shù),fn foo<'a, 'b>(x: &'a i32, y: &'b i32),依此類推。
2、如果只有一個(gè)輸入生命周期參數(shù),那么它被賦予所有輸出生命周期參數(shù):fn foo<'a>(x: &'a i32) -> &'a i32。
3、如果方法有多個(gè)輸入生命周期參數(shù)并且其中一個(gè)參數(shù)是?&self?或?&mut self,說(shuō)明是個(gè)對(duì)象的方法(method), 那么所有輸出生命周期參數(shù)被賦予?self?的生命周期。第三條規(guī)則使得方法更容易讀寫(xiě),因?yàn)橹恍韪俚姆?hào)。
所以前面這個(gè)方法,剛好適用于第二條規(guī)則,所以是可以不用添加生命周期注解的。
關(guān)于第三條規(guī)則,主要體現(xiàn)在結(jié)構(gòu)體方法上,比如:
impl<'a,'b> School<'a,'b>{fn get_school_name(&self,str : &str)->&str{println!("{}",str);self.name.as_ref()}}
這里結(jié)構(gòu)體依然用到前面的學(xué)校結(jié)構(gòu)體,然后有一個(gè)方法,get_school_name
方法,傳入了兩個(gè)引用,同時(shí)返回了引用,本來(lái)根據(jù)生命周期原則,需要指定生命周期注解,但是這里沒(méi)有,依然可以編譯通過(guò)運(yùn)行
這里就是滿足了前面的第三條規(guī)則,輸出生命周期等于self生命周期
一種特殊的生命周期:靜態(tài)生命周期,它可以在程序中一直存活。
靜態(tài)生命周期,用'static表示:比如:
let?s:?&'static?str?=?"I?have?a?static?lifetime";看下面這個(gè)函數(shù):
fn get_max_length<'a , T:Display>(s1 : &'a str ,s2 : &'a str, an : T)->&'a str{println!("{}",an);if s1.len > s2.len(){s1}else{s2}}
這里是一個(gè)使用泛型參數(shù)和生命周期的例子,改方法是可以編譯通過(guò)的,這里僅僅是想說(shuō)明,泛型參數(shù)和生命周期,都是可以一起寫(xiě)在函數(shù)后的尖括號(hào)的!

這里的函數(shù)簽名還可以這么寫(xiě):
fn get_max_length<'a , T>(s1 : &'a str ,s2 : &'a str, an : T)->&'a str where T : Display
即,使用關(guān)鍵字where,在函數(shù)返回值后面跟上泛型實(shí)現(xiàn)的trait
七、Rust錯(cuò)誤處理
在Rust中,如果代碼出現(xiàn)了異常或者錯(cuò)誤怎么辦?其他語(yǔ)言,類似Java、Python都有exception一說(shuō),同時(shí)可以使用try捕獲異常信息。
在Rust中,沒(méi)有捕獲異常,只有panic!宏處理異常。當(dāng)執(zhí)行panic!宏之后,程序會(huì)打印出一個(gè)錯(cuò)誤信息,展開(kāi)并清理?xiàng)?shù)據(jù),然后接著退出
新建panic二進(jìn)制包:
下面嘗試下調(diào)用panic!
panic!("這兒有問(wèn)題!");運(yùn)行:

下面打印了堆棧信息,同時(shí)回溯找到異常來(lái)源。
panic!宏打印堆棧信息,有幾個(gè)參數(shù)設(shè)置
RUST_BACKTRACE=0:可以理解為最簡(jiǎn)單的錯(cuò)誤信息,即只會(huì)打印出程序具體哪一行的錯(cuò)誤信息。
RUST_BACKTRACE=1:稍微多一些的堆棧信息,涵蓋了具體panic!宏具體的錯(cuò)誤信息
RUST_BACKTRACE=full:所有的堆棧信息,也是最為齊全的錯(cuò)誤打印
這里就不挨個(gè)試了
設(shè)置方式,可以通過(guò)命令行,直接在cargo run前面加上這個(gè)參數(shù),即:
$?RUST_BACKTRACE=full cargo run?
開(kāi)發(fā)工具里,就根據(jù)不同開(kāi)發(fā)工具設(shè)置了,我這里使用的是IEDA,直接在edit configurations

這里,使用到的panic!都是會(huì)使程序停止運(yùn)行,稱為不可恢復(fù)錯(cuò)誤,但是有時(shí)候,并不是所有的錯(cuò)誤都是不可恢復(fù)的,比如看下面代碼:
let result = File::open("D:\\1.txt");這里嘗試打開(kāi)D盤下的一個(gè)1.txt文件,如果沒(méi)有這個(gè)文件,應(yīng)該算是異常了,這個(gè)時(shí)候,究竟是否需要panic!一般來(lái)說(shuō)是需要根據(jù)實(shí)際情況來(lái)看的,比如如果沒(méi)有這個(gè)文件或許只是要給個(gè)提示,程序依然需要進(jìn)行下去。
所以File::open()函數(shù)提供了返回值為Result的枚舉,可以根據(jù)實(shí)際的情況來(lái)判斷是否需要panic!
Result枚舉,是Rust提供的枚舉類:
pub type Result<T> = result::Result<T, Error>;T?代表成功時(shí)返回的?Ok?成員中的數(shù)據(jù)的類型,而?E?代表失敗時(shí)返回的?Err?成員中的錯(cuò)誤的類型。因?yàn)?Result?有這些泛型類型參數(shù),可以將?Result?類型和標(biāo)準(zhǔn)庫(kù)中為其定義的函數(shù)用于很多不同的場(chǎng)景
比如:
let?result?=?File::open("D:\\1.txt");let result = match result {Ok(file) => {file}Err(error) => {panic!("打開(kāi)文件失敗:{:?}",error)}};println!("{:?}",result);
這里通過(guò)match匹配模式分別來(lái)處理這兩種情況
上述代碼,在錯(cuò)誤的時(shí)候還是panic!了,其實(shí)很多時(shí)候,是不需要panic!的,比如,根據(jù)不同的錯(cuò)誤信息,做不同的操作:
let?result?=?File::open("D:\\1.txt");let result = match result {Ok(file) => {file}Err(error) => match error.kind() {ErrorKind::NotFound => {}ErrorKind::PermissionDenied => {}ErrorKind::ConnectionRefused => {}ErrorKind::ConnectionReset => {}ErrorKind::ConnectionAborted => {}ErrorKind::NotConnected => {}ErrorKind::AddrInUse => {}ErrorKind::AddrNotAvailable => {}ErrorKind::BrokenPipe => {}ErrorKind::AlreadyExists => {}ErrorKind::WouldBlock => {}ErrorKind::InvalidInput => {}ErrorKind::InvalidData => {}ErrorKind::TimedOut => {}ErrorKind::WriteZero => {}ErrorKind::Interrupted => {}ErrorKind::Other => {}ErrorKind::UnexpectedEof => {}}};println!("{:?}",result);
上述代碼,可以根據(jù)異常的各種原因,針對(duì)性的進(jìn)行操作,這里就不舉例了
失敗時(shí)的錯(cuò)誤簡(jiǎn)寫(xiě):
File::open("D:\\1.txt").unwrap();或者
File::open("D:\\1.txt").expect("打開(kāi)文件錯(cuò)誤!");錯(cuò)誤傳播,當(dāng)調(diào)用一個(gè)函數(shù)時(shí),可能出現(xiàn)錯(cuò)誤,這個(gè)時(shí)候,可以將這個(gè)錯(cuò)誤拋出去,傳播給調(diào)用者,比如:
fn fun_error_spread() -> Result<String,io::Error>{let f = File::open("D:\\1.txt");let mut f = match f {Ok(file) => file,Err(e) => return Err(e),};let mut s = String::new();match f.read_to_string(&mut s) {Ok(_) => Ok(s),Err(e) => Err(e),}}
上述代碼,描述了一個(gè)打開(kāi)并讀取文件為String操作,如果打開(kāi)讀取正常,則返回值Result泛型包含正確的信息,即String,否則返回錯(cuò)誤Error。
同時(shí)錯(cuò)誤傳播還可以簡(jiǎn)寫(xiě)為?
上述代碼也可寫(xiě)為:
fn fun_error_spread() -> Result<String,io::Error>{????let?f?=?File::open("D:\\1.txt")?;let mut s = String::new();f.read_to_string(&mut s)?Ok(s)}
上述代碼量就減小了許多了,但是功能依然等同于前面的方式,這里的?號(hào)等同于錯(cuò)誤的時(shí)候返回的Err,即return Err()
而且在?后面還可以鏈?zhǔn)秸{(diào)用函數(shù),上述代碼還可以改進(jìn):
fn fun_error_spread() -> Result<String,io::Error>{let mut s = String::new();File::open("D:\\1.txt")?.read_to_string(&mut s)?;Ok(s)}
這里是在函數(shù)中調(diào)用,如果直接在主程序中調(diào)用呢?
fn main() -> Result<(), Box<dyn Error>>{File::open("D:\\1.txt")?;????Ok(())}
在main函數(shù)中調(diào)用錯(cuò)誤傳播,需要在main函數(shù)中添加返回值Result。
這里的()表示返回正確的任意數(shù)據(jù),
同時(shí)Box<dyn Error>被稱為"trait對(duì)象"("trait object"),這個(gè)在后面會(huì)講到,這里可以理解?Box<dyn Error>?為使用???時(shí)?main?允許返回的 “任何類型的錯(cuò)誤”。
這里是關(guān)于Rust的錯(cuò)誤的常見(jiàn)處理方式,那么在實(shí)際的項(xiàng)目中,是否需要panic!或者不panic!,這個(gè)取決于自己的邏輯決定。
八、編寫(xiě)測(cè)試代碼
前面寫(xiě)到的所有函數(shù),方法,模塊兒,結(jié)構(gòu)體,泛型,trait等等,寫(xiě)測(cè)試類的時(shí)候,都是在二進(jìn)制的crate中進(jìn)行的,這樣子其實(shí)很不方便,很多時(shí)候,希望可以靈活的測(cè)試我們正在寫(xiě)的任何一段代碼,而不想多余的新建代碼文件這個(gè)時(shí)候,測(cè)試代碼就來(lái)了。
Rust提供了代碼測(cè)試方法。
在項(xiàng)目中新建crate庫(kù)(非二進(jìn)制)my_test
這時(shí)候,可以看見(jiàn)Rust在lib.rs中已經(jīng)幫我們寫(xiě)好了一段代碼:
#[cfg(test)]mod tests {#[test]fn it_works() {+ 2, 4);}}
這里的#[cfg(test)]表明crate庫(kù)是一個(gè)測(cè)試庫(kù),同時(shí)在函數(shù)it_works中前面有一個(gè)#[test],表示這是一個(gè)測(cè)試函數(shù)。
assert_eq!宏來(lái)斷言2+2是否等于4
同時(shí)測(cè)試函數(shù)是可以運(yùn)行的:

如上圖所示,mod前的運(yùn)行按鈕,可以保證模塊兒下的所有測(cè)試函數(shù)都會(huì)執(zhí)行一次,函數(shù)上的運(yùn)行按鈕,則保證當(dāng)前函數(shù)執(zhí)行一次,如果執(zhí)行有問(wèn)題,則運(yùn)行按鈕會(huì)出現(xiàn)紅色顏色區(qū)分,控制臺(tái)會(huì)異常。
比如:

這里是執(zhí)行的mod上的運(yùn)行,第一個(gè)函數(shù)執(zhí)行錯(cuò)誤,第二個(gè)執(zhí)行正確,同時(shí)第一個(gè)的錯(cuò)誤堆棧信息都打印出來(lái)了
關(guān)于測(cè)試的一些宏,下面舉例常見(jiàn)的一些宏,其他的可以參照官方API文檔
assert_eq!,斷言是否相等,比如上面的例子
assert!,斷言獲取結(jié)果是否為true,為false時(shí)panic!,比如assert!(true)
assert_ne!,和assert_eq!恰好相反
#[should_panic]檢查代碼是否按照期望處理錯(cuò)誤
單元測(cè)試類比較簡(jiǎn)單,但是很實(shí)用,官方提供了很多宏,可以查看官方API。
https://doc.rust-lang.org/std/index.html
編寫(xiě)好測(cè)試類可以更能保證代碼的安全和正確性。
本期分享內(nèi)容有點(diǎn)多,得多寫(xiě)代碼溫習(xí)下!(#^.^#)
基本上,Rust的基礎(chǔ)就到這兒了,之后會(huì)有一些中高級(jí)一些的Rust,比如多線程、IO、智能指針等等。
點(diǎn)擊下面公眾號(hào)獲取更多
歡迎點(diǎn)贊轉(zhuǎn)發(fā),謝謝
