<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          懂你,更懂Rust系列之集合、泛型、trait(接口)、生命周期和錯(cuò)誤處理

          共 24717字,需瀏覽 50分鐘

           ·

          2021-07-29 09:21


          總在剎那間有一些了解

          說(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包。


          ba395cebec4a328e185c4654774945ca.webp


          一、泛型


          首先思考一個(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ō)了很多了。


          88c5751a798b9cc3d1f54a0b45e9c0fd.webp


          那么接下來(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ù)代碼了:


          1735ab731531bba1f1d4452aa8f46c64.webp


          同樣的,可以在結(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)行


          8086fff0658165005be7a726c7d3f3a0.webp


          看下面這個(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)行:


          66b1b68b9b56e2ac1acecd59caad159a.webp


          前面已經(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


          e2c957fc25b0e4980926cf6c879d5822.webp


          首先要介紹的第一個(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ò):


          825009aaa04df450c65a76d09c708ca1.webp


          修改上述代碼:


          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)行:


          6dbfc07a00adeae43694d9891bd3fc7f.webp


          遍歷集合元素可以使用for循環(huán):


          let v: Vec<i32> = vec![13,25,159,1336];for &i in &v{    println!("{}",i);}


          編譯運(yùn)行:


          56fc5ca16c3cdce57ed29db16ef0b24f.webp


          也可以嘗試對(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)行:


          9aec7f2ec502acb84db374384fa1b752.webp


          這里通過(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)行:


          a40b800ae70ce54550803dffdb7e5dd9.webp



          三、字符串


          新建一個(gè)str二進(jìn)制庫(kù):


          6c0297839e860c65659fa7131e7dc44e.webp


          字符串其實(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ù)中還包含一系列其他字符串類型,比如?OsStringOsStr、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)行


          1791eb4a7a73d751628932455dbe04d8.webp


          也可以直接,將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、s2s3都是有效的


          但是使用+,則首個(gè)字符串是會(huì)發(fā)生移動(dòng)的


          698fa883048ee4726ee14c3bb571d4cf.webp


          但是后者不會(huì)


          2eca3808eebe5c9e217995f7f45a7214.webp


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


          c948dd23ed1155a1d5d73cf56beed647.webp


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


          6c1f9fe0f1fef925a7a2c0f0ce3b0341.webp


          很遺憾,報(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)量值。


          7e4089dd2609c97726a2bd833d430656.webp


          即此時(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)制包


          683dfcc972c269992c3c271753fbaf9b.webp


          哈希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é)果:


          83c74a1e14a20775e077f86b7707ba15.webp


          插入堆上數(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ò):


          d983769011770f1ea5cb1535126ad7b2.webp


          有的時(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é)果:


          f17931d5bfd3cefc30da822e237226ac.webp



          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


          1ff026eed21acd61b4eacc825d7688e4.webp



          有點(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)行看看


          53cc8ee976c7564f79a4348fc98ff841.webp


          首先這里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源碼


          e916460f949abcc7d11ecf637005ef92.webp


          這里的是一個(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)行:


          8beeeafdd9710f46cbe7e0105fe3f54c.webp


          結(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)行:


          cb6286b39168aea37c81950c89715a03.webp



          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ò):


          37f367d131cb015b628cea980d42f719.webp



          編譯器說(shuō),租借的值,沒(méi)能活得足夠長(zhǎng)


          用下面這個(gè)圖來(lái)表示:


          53f6c3fddf2ac170c3d9a162e94301c6.webp


          '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ò)。



          2aa79738b4d883a0d107ab0250e3cab5.webp


          然后看這里編譯器提出了一個(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ù)s1s2最小生命周期是一樣的,即返回值生命周期與參數(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)行:


          9c25101292605674b22ea9d8305aeef0.webp


          仔細(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),那么是否可以不需要指定生命周期注解呢?


          041d66b4c0715e999f809dd7074dff3d.webp


          這里是也是編譯不通過(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)行:


          e267df7ff8cb3a568200ff816e87eab3.webp


          結(jié)果體參數(shù)表明,所有引用的對(duì)象,其存活時(shí)間都一定要比結(jié)構(gòu)體大。


          比如,如果下面這樣就會(huì)編譯錯(cuò)誤:


          cc3238c1cf49d1ea977b4e96a575e0e0.webp


          很明顯,上述代碼,結(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)有指定生命周期注解:


          a879bb127f4fbb86a205df1b936f99a0.webp


          上述代碼也是可以通過(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)的!


          a817d9ccba31771af8ced14d9b8c7a4a.webp


          這里的函數(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)行:


          d9b775dabcb9b3da4c5db13d3b6d334d.webp


          下面打印了堆棧信息,同時(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


          c10bdfa2cab4aa89c3ef2a5578c4a2b3.webp


          這里,使用到的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() {        assert_eq!(2 + 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)行的:


          27d83c085195f916fe6b76363a475e41.webp


          如上圖所示,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ì)異常。


          比如:


          19b724d5f7c1789c9e340d04bd4da8cf.webp



          這里是執(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ā),謝謝

          瀏覽 53
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  神马午夜1| 被错误羁押6千天男子申请国赔1911万 | 日欧美视频在线 | 婷婷综合网站 | 操逼片儿 |