集合組織特性相同的數(shù)據(jù);泛型可以定義任何抽象數(shù)據(jù)類型;生命周期限制所有權(quán)的作用域范圍;錯(cuò)誤處理使程序更健壯。
一組特性相同的數(shù)據(jù)集合,除了基本數(shù)據(jù)類型的元組、數(shù)組。rust 標(biāo)準(zhǔn)庫(kù)提供了一些非常有用的數(shù)據(jù)結(jié)構(gòu)。
(相關(guān)資料圖)
Vector
存儲(chǔ)列表通過(guò)類型Vec
定義。只能存儲(chǔ)相同類型的值,在內(nèi)存中彼此相鄰排列存儲(chǔ)。
let v:Vec = Vec::new();
通過(guò)Vec::new()
創(chuàng)建一個(gè)類型實(shí)例。因?yàn)闆](méi)有初始化任何類型數(shù)據(jù),就必須指定數(shù)據(jù)類型。定義集合實(shí)例就只允許存儲(chǔ)指定的類型數(shù)據(jù)。
另一種方便創(chuàng)建集合實(shí)例的方式通過(guò) rust 提供的vec!
宏
let v = vec![3,5,6];
定義了實(shí)例v
,可以初始化數(shù)據(jù),rust 會(huì)推導(dǎo)出數(shù)據(jù)的類型。示例中默認(rèn)推導(dǎo)出類型是 i32
可以通過(guò)內(nèi)部方法,操作實(shí)例來(lái)添加、修改里面的數(shù)據(jù)
v.push(val)
添加值。
v.get(index)
獲取值。會(huì)得到一個(gè)可用于match
匹配的Option<&T>
也可以使用索引取值&v[1]
。使用索引取值,如果超出最大索引,會(huì)報(bào)錯(cuò);使用get()
方法會(huì)返回None
v.insert(index,val)
向指定 index 位置插入數(shù)據(jù)。
v.remove(index)
移除指定 index 位置的數(shù)據(jù),并返回該數(shù)據(jù)。
v.pop()
移除最后一個(gè)元素,并返回。
v.clear()
清空實(shí)例。移除所有元素。
v.len()
返回當(dāng)前數(shù)據(jù)個(gè)數(shù)。
要可編輯實(shí)例,聲明必須使用mut
可變。
let mut v:Vec = vec![];// 更新值v.push(23);v.push(4);v.push(15);v.push(56);// 取值v.get(2); // 4v[2]; // 4
在操作vec
時(shí),注意引用所有權(quán)的轉(zhuǎn)義。最好的方式就是只是值借用&v
。
通過(guò)for
循環(huán)來(lái)遍歷 vector 中的值。
for i in &v{ println!("{i}");}
在遍歷時(shí)實(shí)例v
不能插入、刪除項(xiàng)。如果需要想遍歷修改每一項(xiàng)值,可以傳遞可變引用
for i in &mut v { *i += 5; println!("{i}");}
因?yàn)槭菍?duì)值做操作。通過(guò)*
解引用取到指針指向的值。再次從實(shí)例v
取值時(shí),都是最新計(jì)算過(guò)的值。
因?yàn)?vector 只能存儲(chǔ)相同類型的值。實(shí)際開(kāi)發(fā)中如果需要存儲(chǔ)不同類型的值,可以使用枚舉定義。
這樣對(duì)于 vector 而言,它都是同一種枚舉類型。
enum Color{ Red(String), Green(u32,u32,u32), Green(u32,u32,u32,u8)}fn main(){ let colors = vec![Color::Red(String::from("red")), Color::Green(0, 255, 0)]; for i in &colors { println!("{:?}", i); }}
之前已經(jīng)通過(guò)String::from()
來(lái)創(chuàng)建一個(gè)字符串變量值。字符串是字節(jié)的集合。
在 rust 中只有一種字符串類型:字符串 slice str
;通常是以借用的方式&str
。
作為一個(gè)集合,也可以通過(guò) new 操作符創(chuàng)建一個(gè)實(shí)例。
let mut str = String::new();
但是通過(guò) new 創(chuàng)建是的實(shí)例不能初始化數(shù)據(jù)值。所以之前一直使用String::from()
也可以用一個(gè)字符串字面值創(chuàng)建 String
let s = "hboot";let str = s.to_string();
字符串是utf-8
編碼的。可以包含任何可以正確編碼的數(shù)據(jù)。
操作字符串,作為一個(gè)集合,也有許多更新的方法:
push_str
尾部附加字符串。不會(huì)獲得變量的所有權(quán),內(nèi)部采用字符串 slice。push
尾部附加字符。let mut s = String::from("hboot");s.push_str(" hello");s.push("A");
也可以通過(guò)+
運(yùn)算符拼接字符串,運(yùn)算位加值將會(huì)轉(zhuǎn)義所有權(quán),而被加值則必須引用
let s1 = String::from("hboot");let s2 = String::from("hello");let s = s1+&s2; // s1的所有權(quán)沒(méi)有了,s2的所有權(quán)仍然存在
也就是只能是&str
和String
相加。不能兩個(gè)String
相加,它們類型不同,確定相加是因?yàn)?rust 內(nèi)部把 String 強(qiáng)制轉(zhuǎn)換為&str
當(dāng)拼接值過(guò)多時(shí),我們可以通過(guò)format!
宏來(lái)處理。它不會(huì)獲取任何字符串的所有權(quán)
let s1 = String::from("hboot");let s2 = String::from(" hello");let s3 = String::from(" world");let s = format!("{s1}{s2}{s3}");
rust 的字符串不支持索引。
所以遍歷字符串最好的方式明確需要的是字符還是字節(jié)。字符通過(guò)chars
方法將其分開(kāi)并返回多個(gè)char
類型的值;字節(jié)則使用bytes
方法返回字符的編碼值。
let s1 = String::from("hboot");// 遍歷獲取字符for c in s1.chars() { println!("{c}");}// 遍歷獲取字節(jié)for c in s1.bytes() { println!("{c}");}
對(duì)于字節(jié),有的語(yǔ)言編碼后可能不止一個(gè)字節(jié)組成,這個(gè)需要注意。
HashMap
存儲(chǔ)鍵值對(duì)創(chuàng)建HashMap
實(shí)例,因?yàn)?HashMap 沒(méi)有被 prelude。所以需要手動(dòng)引入。
use std::collections::HashMap;fn main(){ let mut map = HashMap::new();}
當(dāng)未被使用時(shí),鍵值對(duì)的數(shù)據(jù)類型是unknown
。在第一次插入數(shù)據(jù)后,則決定了后面的數(shù)據(jù)類型
let mut map = HashMap::new();map.insert(1, 10);map.insert(2, 30);
此時(shí)默認(rèn)類型為HashMap
。當(dāng)時(shí)用 String 作為鍵值是,變量的所有權(quán)將被轉(zhuǎn)移給 map。字符串變量不可用
let mut map = HashMap::new();let s = String::from("red");map.insert(s, "red");
通過(guò)map.get()
獲取 HashMap 中的值,返回Option<&V>
,如果沒(méi)有鍵時(shí),則返回None
.
可以通過(guò)copied()
方法來(lái)獲取Option
;如果沒(méi)有鍵時(shí),可以通過(guò)uwrap_or()
在沒(méi)有鍵值時(shí),設(shè)置一個(gè)替代值。
map.get(&String::from("yellow")).copied().unwrap_or("yellow");
注意get
方法接受是一個(gè)&str
類型。
當(dāng)我們重復(fù)對(duì)同一個(gè)鍵賦值時(shí),后面的會(huì)覆蓋之前的。如果需要判斷是否存在鍵,不存在插入數(shù)據(jù);存在則不做任何操作
map.entry(String::from("green")).or_insert("green");
entry
的or_insert()
方法在鍵存在時(shí)會(huì)返回這個(gè)值的可變引用。不存在則將參數(shù)作為新值插入并返回值的可變引用。
一個(gè)示例,通過(guò) HashMap
統(tǒng)計(jì)字符串中出現(xiàn)的字符數(shù)。
let s1 = String::from("hboot");let mut map = HashMap::new();for c in s1.chars() { let num = map.entry(c).or_insert(0); *num += 1;}dbg!("{:?}", map);
HashMap
默認(rèn)使用了叫做 SipHash 的哈希函數(shù),可以抵御哈希表的拒絕服務(wù)攻擊。
泛型是具體類型和其他屬性的抽象替代。定義時(shí)不必知道這里實(shí)際代表什么,比如之前的實(shí)例中的Option
都已經(jīng)接觸了。
通過(guò)定義泛型,可以抽離一些重復(fù)的代碼邏輯。使得我們的代碼更具維護(hù)性、適應(yīng)性更強(qiáng)。
創(chuàng)建一個(gè)泛型函數(shù)。類型參數(shù)聲明必須在函數(shù)名稱和參數(shù)列表中間尖括號(hào)<>
里面。
fn largest(list: &[T]) -> &T { let mut large = &list[0]; for val in list { if val > large { large = val; } } large}fn main(){ let v1 = vec![12, 34, 5, 56, 7]; let v2 = vec![34.23, 12.12, 56.1223, 23.12]; dbg!(largest(&v1)); dbg!(largest(&v2));}
實(shí)例中為了找出給定 vector 結(jié)構(gòu)數(shù)據(jù)中的最大值。但是調(diào)用的兩次結(jié)構(gòu)實(shí)例是不同的數(shù)據(jù)類型i32、f64
,使用泛型則可以只寫(xiě)一個(gè)公用的函數(shù)。
泛型函數(shù)中通過(guò)遍歷結(jié)構(gòu)中的數(shù)據(jù)進(jìn)行對(duì)比排序。但是
泛型是任何類型,存在有的數(shù)據(jù)類型不能進(jìn)行排序,rust 在編譯階段會(huì)報(bào)錯(cuò)。所以增加了泛型限制,std::cmp::PartialOrd
標(biāo)識(shí)傳入的類型都可以進(jìn)行排序。
在結(jié)構(gòu)體使用泛型,作為數(shù)據(jù)類型。
struct Size{ width:T, height:T}
也可以傳入多個(gè)泛型,對(duì)應(yīng)不同的字段數(shù)據(jù)類型Size
在枚舉中使用泛型。之前已經(jīng)使用的枚舉Option
enum Status { YES(T), NO(U),}
也可以在結(jié)構(gòu)體、枚舉的方法定義中使用泛型。此時(shí)需要在impl
后聲明泛型T
impl Size { fn width(&self) -> &T { &self.width }}
如果在方法中,指定了具體的數(shù)據(jù)類型,那么創(chuàng)建的實(shí)例,不是該數(shù)據(jù)類型時(shí),則不能調(diào)用該方法。
impl Size { fn height(&self) -> &u8 { &self.height }}fn main(){ let size1: Size = Size { width: 34, height: 45, }; let size2: Size = Size { width: 34.12, height: 45.34, }; size1.height(); // size1 實(shí)例上有height方法。size2則沒(méi)有}
泛型不會(huì)使程序比具體類型運(yùn)行的慢。rust 通過(guò)在編譯時(shí)進(jìn)行泛型代碼的單態(tài)化,也就是重復(fù)將泛型聲明為具體的定義。
trait
定義共同行為什么是 trait,在之前的描述已多次出現(xiàn)。它定義了某個(gè)特定類型擁有可能與其他類型相同的功能。
trait
以一種抽象的方式定義共享的行為。trait bounds
指定泛型是任何擁有特定行為的類型。類比接口行為。抽象定義屬性、方法,然后其他的實(shí)例創(chuàng)建實(shí)現(xiàn)接口中的方法。
通過(guò)trait
定義一個(gè)抽象方法。
trait Log { fn log(&self)->String;}
聲明一個(gè)Log
tait,包含了一個(gè)方法 log。它用來(lái)記錄實(shí)例創(chuàng)建產(chǎn)生行為后日志記錄。
每個(gè)聲明的集合數(shù)據(jù)都必須實(shí)現(xiàn)這個(gè)方法。
struct Size { width: T, height: T,}// std::fmt::Debug 是為了打印輸出impl Log for Size { fn log(&self) -> String { let str = format!("{:?}-{:?}", &self.width, &self.height); println!("變更值:{str}"); str }}fn main(){ let mut size2: Size = Size { width: 34.12, height: 45.34, }; size2.width = 45.111; size2.log();}
也可以提供一個(gè)默認(rèn)實(shí)現(xiàn),這樣可以選擇重載這個(gè)方法或者保留默認(rèn)實(shí)現(xiàn)。
trait Log { fn entry_log(&self) -> String { String::from("entry log...") }}
然后在其他類型實(shí)現(xiàn) trait 時(shí),可以保留默認(rèn)的行為。
// 在上方實(shí)現(xiàn)的結(jié)構(gòu)體size2,可以直接調(diào)用println!("{}", size2.entry_log());
也可以在默認(rèn)實(shí)現(xiàn)中,調(diào)用其他方法。
trait Log { fn log(&self) -> String; fn entry_log(&self) -> String { let entry = String::from("entry log..."); println!("{}", entry); // 調(diào)用log方法 let content = self.log(); format!("{}", content) }}fn main(){ // size2 實(shí)現(xiàn)不變,僅需要調(diào)用entry_log方法即可 // entry.log(); size2.entry_log();}
實(shí)現(xiàn)了trait
這些定義后,如何將其作為參數(shù)傳遞呢。使用impl trait
語(yǔ)法
fn notify(item: &impl Log) { println!("Log! {}", item.entry_log());}fn main(){ // 通過(guò)傳遞實(shí)例 size2直接調(diào)用該方法 notify(&size2);}
也可以通過(guò)泛型來(lái)定義參數(shù),專業(yè)術(shù)語(yǔ)稱為trait bound
fn notify(item: &T) { println!("Log! {}", item.entry_log());}
這種方式在對(duì)于多個(gè)參數(shù)的書(shū)寫(xiě)友好??梢酝ㄟ^(guò)泛型限制參數(shù)的類型。
fn notify(item: &T,item1:&T) { println!("Log! {}", item.entry_log());}
也可以通過(guò)+
指定多個(gè) trait。
fn notify(item: &(impl Log + Display)) {}// 或者使用泛型fn notify(item: &T) {}
調(diào)用傳參時(shí)的實(shí)例則必須實(shí)現(xiàn)Log和Display
,但是當(dāng)有很多個(gè) trait 時(shí),書(shū)寫(xiě)起來(lái)就會(huì)很多。
可以通過(guò)where
關(guān)鍵字簡(jiǎn)化書(shū)寫(xiě),看起來(lái)更加的清晰。
fn notify(item: &T, item2: &U)where T: Log + Display, U: Clone + Display,{}
也可以通過(guò)函數(shù)返回某個(gè)實(shí)現(xiàn)了trait
的類型實(shí)例
fn return_log() -> impl Log { Size { width: 23, height: 45, }}fn main(){ let size3 = return_log(); size3.entry_log();}
在閉包和迭代器場(chǎng)景中十分有用。但是這種適用于返回單一類型的情況。
通過(guò)trait bound
可以有條件的控制實(shí)例可調(diào)用的類型方法。只有類型實(shí)現(xiàn)了某些方法,實(shí)例才會(huì)有指定的方法。
也就是對(duì)于引用、借用的有效作用域的限制。在引用或借用之前,保證被引用或借用的變量在當(dāng)前作用域一直有效。
這個(gè)特性避免了懸垂引用,防止了程序引用未定義數(shù)據(jù)的問(wèn)題;如下例子:
fn main(){ let a; { let b = "admin"; a = &b; } println!("{}",a)}
運(yùn)行cargo run
這段代碼,將會(huì)報(bào)錯(cuò),變量a
得到了局部作用域變量b
的引用,在最后的作用域中使用了a
。但是變量b
在局部作用域結(jié)束時(shí)就已經(jīng)釋放了,導(dǎo)致引用它的a
在使用時(shí)就會(huì)報(bào)錯(cuò)。
在 rust 中,通過(guò)借用檢查器
來(lái)檢測(cè)作用域之間的借用是否都是有效的。并在編譯階段給出錯(cuò)誤提示,上面的代碼不需要運(yùn)行,也可以看到編譯器給出的錯(cuò)誤提示。
為了解決上面這問(wèn)題,我們可以將 b
的所有權(quán)交出去,因?yàn)?code>b作用域結(jié)束,并沒(méi)有什么作用了。
{ let b = "admin"; a = b;}
還有在第一篇文章所有權(quán)
的問(wèn)題
fn print_info() -> String { let str = String::from("hboot"); // 這是錯(cuò)誤的,函數(shù)執(zhí)行完畢,必須交出所有權(quán) // &str // 直接返回創(chuàng)建的字符串 str}
還有一些問(wèn)題,在函數(shù)調(diào)用的時(shí)候,需要傳參處理完后返回某個(gè)參數(shù)的值。如下示例:
fn main(){ let a = String::from("abcd"); let b = String::from("efg"); println!("{}", longest(&a, &b));}fn longest(a: &str, b: &str) -> &str { if a.len() > b.len() { a } else { b }}
編譯器直接就會(huì)提示錯(cuò)誤信息,我們執(zhí)行cargo run
看詳細(xì)的錯(cuò)誤信息。錯(cuò)誤也很明確expected named lifetime parameter
,并且給出了解決示例。
fn longest<"a>(a: &"a str, b: &"a str) -> &"a str { if a.len() > b.len() { a } else { b }}
"a
就是生命周期的注解語(yǔ)法。
a、b
的生命周期。"
撇號(hào)開(kāi)頭,小寫(xiě)的名稱。&
之后,它表明了被借用的變量存在的時(shí)間和借用變量的生命周期存在的一樣久a、b
中作用域最短的那個(gè)生命周期結(jié)束之前有效需要注意的的就是最后一個(gè),它的存在時(shí)間長(zhǎng)久在于作用域最短的那一個(gè)
fn main(){ let a = String::from("abcd"); let result; { let b = String::from("efg"); result = longest(&a, &b); } println!("{}", result);}
函數(shù)的調(diào)用在b
的局部作用域中,調(diào)用結(jié)束后的結(jié)果值result
使用超出了b
的作用域,編譯器報(bào)錯(cuò)。
可以把result
的使用范圍局限在b
的作用域內(nèi)。
{ let b = String::from("efg"); result = longest(&a, &b); println!("{}", result);}
同設(shè)置泛型一樣,在結(jié)構(gòu)體名稱后面使用簡(jiǎn)括號(hào)<>
聲明泛型生命周期。
struct User<"b> { name: &"b str,}fn main(){ let name = String::from("hboot"); let user = User { name: &name };}
結(jié)構(gòu)體的實(shí)例user
的生命周期不能比字段name
的引用存在的更久。
生命周期注解是為了 rust 檢查器推斷出引用的生命周期。有時(shí)候就會(huì)書(shū)寫(xiě)大量的這種模板式的注解,這種場(chǎng)景有時(shí)候 rust 會(huì)納入到編譯器中,這樣就不在顯示聲明,而這些模式統(tǒng)稱為生命周期省略規(guī)則
。我們?cè)跁?shū)寫(xiě)時(shí),只要總訓(xùn)這些規(guī)則就可以不用聲明式書(shū)寫(xiě)生命周期了。
編譯器推斷生命周期的規(guī)則:
&self
或&mut self
,所有輸出生命周期被賦予 self 的生命周期。static
靜態(tài)生命周期通過(guò)static
聲明一個(gè)靜態(tài)生命周期,它存活于整個(gè)程序運(yùn)行期間。
let str:&"static str = "hello rust";
str
文本直接存儲(chǔ)在程序的二進(jìn)制文件中。在使用時(shí)考慮是否真的需要。
create
通過(guò)拆解模塊來(lái)創(chuàng)建多個(gè)文件組織代碼。更好的重用代碼,定義哪些內(nèi)容可以公開(kāi),哪些是私有的。
這里有一些概念:包
- Cargo 的一個(gè)功能,允許構(gòu)建、測(cè)試和分享 crate。crates
- 一個(gè)模塊的樹(shù)形結(jié)構(gòu),它形成了庫(kù)或者二進(jìn)制項(xiàng)目。模塊/use
- 允許控制作用域和路徑的私有性。路徑
- 命名例如結(jié)構(gòu)體、函數(shù)或模塊等方式
crate
分為庫(kù)和二進(jìn)制。二進(jìn)制可以被編譯為可執(zhí)行文件,有一個(gè)main
函數(shù)來(lái)執(zhí)行程序需要做的事情;庫(kù)用來(lái)作為工具,提供諸如函數(shù)的功能。
包是一些列功能的一個(gè)或多個(gè)crate
。包含Cargo.toml
文件,闡述如何去構(gòu)建這些 crate。
往往src/lib.rs
就表示這是一個(gè)庫(kù);而src/main.rs
表示這是一個(gè)包。這也是編譯時(shí)的入口點(diǎn)。
通過(guò)mod
聲明一個(gè)模塊,通過(guò)內(nèi)聯(lián)方式聲明mod user{}
;或者創(chuàng)建文件src/user.rs
或者src/user/mod.rs
// 內(nèi)聯(lián)聲明mod user {}
聲明好模塊后,要想在其他地方使用該模塊,則需要加pub
修飾,標(biāo)識(shí)這是一個(gè)公用模塊。
假設(shè)現(xiàn)在我們有以文件創(chuàng)建的模塊src/user.rs
,其中有兩個(gè)聲明的公用結(jié)構(gòu)和枚舉類。
pub struct model { name: String, age: i32,}pub enum status { online, offline,}
通過(guò)mod
關(guān)鍵字定義,來(lái)說(shuō)明編譯器在src/user.rs
查找代碼。
// 在main.rs中mod user;fn main(){ // 可以直接通過(guò)模塊名稱來(lái)使用定義在模塊中的類型 let status = user::status::offline;}
也可以通過(guò)use
關(guān)鍵字來(lái)導(dǎo)入需要使用的公用類型。
// 在main.rs中use crate::user::status;mod user;fn main(){ let status = status::offline;}
在一個(gè)模塊中,也可以繼續(xù)聲明子模塊。聲明的方式同上
剛才使用模塊引入的方式crate::user
是以 crate 跟開(kāi)頭的全路徑。也可以相對(duì)于當(dāng)前模塊開(kāi)始,以self
或者super
// 在main.rs中use user::status;mod user;
由于模塊 user 和main.rs
是在同一路徑下,所以可以通過(guò)相對(duì)路徑引入。
如果模塊層級(jí)嵌套,不在同一路徑下,要想使用相對(duì)路徑,可通過(guò)super
相當(dāng)于..
,從父模塊的路徑引入。
// 在模塊user中定義子模塊mod work { use super::model; use super::status; fn is_working(user: model) -> String { match user.status { status::online => String::from("在線"), status::offline => String::from("離線"), } }}
雖然它們?cè)谕晃募?,但?code>work定義為子模塊,有自己的作用域。所以不能直接訪問(wèn)父模塊中定義的類型??赏ㄟ^(guò)super
引用。
self
則表示自己,調(diào)用自己模塊中的定義。
mod work { use super::model; use super::status; fn is_working(user: model) { // 直接調(diào)用 init_user(); // 通過(guò)self調(diào)用 self::init_user(); } fn init_user() {}}
pub
聲明的公用方法、類型,對(duì)于結(jié)構(gòu)體,它的字段卻是私有的。如果想要?jiǎng)?chuàng)建實(shí)例,則必須聲明字段為公用。
// 在main.rs中use user::{model, status};mod user;fn main(){ // 下面這個(gè)創(chuàng)建時(shí)編譯不過(guò)的,錯(cuò)誤提示字段私有。 let u = model { name: String::from("admin"), age: 35, status: status::offline, };}
但是對(duì)于子模塊引入使用時(shí),這些字段默認(rèn)都是有效可用的。
mod work { use super::model; use super::status; fn init_user() { let user = model { name: String::from("hboot"), age: 34, status: status::online, }; }}
對(duì)于外部引入模塊的結(jié)構(gòu)體時(shí),如果有私有屬性,則需要提供實(shí)例化方法。
// 調(diào)整src/user.rs ,提供實(shí)例化方法pub struct model { pub name: String, age: i32, status: status,}impl model { pub fn new(name: String) -> Self { Self { name, age: 35, status: status::online, } }}// 在src/main.rsfn main(){ let u = model::new(String::from("hboot"));}
通過(guò)use
引入,如果遇到同名類型時(shí),引入路徑可以只寫(xiě)到模塊名稱,然后通過(guò)模塊名稱調(diào)用方法、類型。
// 在main.rs中use user::work;mod user;fn main(){ let u2 = work::init_user(); print!("{:?}", u2)}
注意上面實(shí)例可被打印,修改模塊 user 定義的結(jié)構(gòu)體、枚舉#[derive(Debug)]
也可通過(guò) as
關(guān)鍵字提供一個(gè)別名。
// 在main.rs中use user::work::init_user as initUser;mod user;fn main(){ let u2 = initUser();}
可通過(guò)pub use
繼續(xù)導(dǎo)出到外部作用域使用。這可以避免路徑過(guò)長(zhǎng)引入,可以將子模塊的定義導(dǎo)入到父級(jí)模塊中。再重導(dǎo)出。
pub use user::work;
這樣對(duì)于當(dāng)前作用域路徑的上級(jí),可以繼續(xù)導(dǎo)入 work 模塊使用。
當(dāng)一個(gè)功能模塊子模塊很多時(shí),就需要從一個(gè)模塊中導(dǎo)出很多的類型、結(jié)構(gòu)體、方法等。就會(huì)出現(xiàn)很多use
行,使用嵌套路徑路徑消除這種引入。
上面的示例已經(jīng)展示了如何引入多個(gè)定義類型。
use user::model;use user::status;// 嵌套一行搞定use user::{model, status};
也可以通過(guò) glob 運(yùn)算符*
導(dǎo)入所有的公用項(xiàng)。
use crate::user::*
在程序遇到錯(cuò)誤時(shí),分為可恢復(fù)和不可恢復(fù)??苫謴?fù)問(wèn)題比如訪問(wèn)數(shù)據(jù)、文件未訪問(wèn)到,可通過(guò)日志方式告知用戶;不可恢復(fù)問(wèn)題比如越界,需要中止執(zhí)行。
通過(guò)Result
處理可恢復(fù)的錯(cuò)誤;panic!
宏處理不可恢復(fù)的錯(cuò)誤。終止程序執(zhí)行。
通過(guò)panic!
可以直接拋出一個(gè)錯(cuò)誤。
fn main() { panic!("hello world");}
程序執(zhí)行到此處會(huì)終止執(zhí)行,并報(bào)出錯(cuò)誤打印從出 hello world。可以看到錯(cuò)誤出現(xiàn)的代碼位置信息
通過(guò)錯(cuò)誤提示,可以設(shè)置環(huán)境變量RUST_BACKTRACE=1
,查看調(diào)用棧信息
$> RUST_BACKTRACE=1 cargo run
發(fā)布生產(chǎn)環(huán)境包時(shí),可以將panic
禁止掉,從而得到更小的二進(jìn)制文件。
# Cargo.toml[profile.release]panic="abort"
有一些錯(cuò)誤不影響程序允許的情況,我們需要給出錯(cuò)誤時(shí)得處理方案。
Result
枚舉類,標(biāo)識(shí)程序方案按預(yù)期或者錯(cuò)誤。
enum Result { Ok(T), Err(E),}
Result
及其成員被提前導(dǎo)入。
比如讀取文件時(shí),如果文件存在,則讀取成功,狀態(tài)為Ok
,類型 T 則為std::fs::File
文件
use std::fs::File;fn main(){ let read_fs = File::open("hello.txt"); // 通過(guò)match let read_file = match read_fs { Ok(file) => file, Err(error) => panic!("error info:{:#?}", error), };}
因?yàn)槲覀兾募夸浵聸](méi)有hello.txt
文件,就會(huì)執(zhí)行中斷,報(bào)錯(cuò)。我們處理錯(cuò)誤時(shí),如果是文件未找到,則直接從創(chuàng)建一個(gè)。
use std::fs::File;use std::io::ErrorKind;fn main(){ let read_fs = File::open("hello.txt"); let read_file = match read_fs { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(f) => f, Err(err) => panic!("error in create file:${:?}", err), }, other_error => { panic!("error info:{:#?}", error) } }, };}
執(zhí)行后,在項(xiàng)目根目錄下會(huì)生成hello.txt
文件。如果是沒(méi)有權(quán)限訪問(wèn)時(shí),則還是打印輸出錯(cuò)誤。
我們寫(xiě)了很多的match
來(lái)處理不同的情況,這看起來(lái)很讓人難以理解。通過(guò)unwrap
和expect
簡(jiǎn)寫(xiě)處理
unwrap()
方法調(diào)用,如果文件訪問(wèn)到,則返回Ok
;讀取不到則返回Err
.
let read_file = File::open("hello.txt").unwrap();
通過(guò)expect()
方法調(diào)用可以達(dá)到同樣的功能。但是它允許我們自定義錯(cuò)誤信息。
let read_file = File::open("hello.txt").expect("無(wú)法讀取 hello.txt!!!");
有一些錯(cuò)誤在每個(gè)方法中處理重復(fù)、麻煩??梢詫㈠e(cuò)誤信息傳遞到調(diào)用方,然后統(tǒng)一處理。
use std::fs::File;use std::io::{self, ErrorKind, Read};fn read_file() -> Result { let file_result = File::open("hello.txt"); let mut file_name = match file_result { Ok(file) => file, Err(e) => return Err(e), }; let mut name = String::new(); match file_name.read_to_string(&mut name) { Ok(_) => Ok(name), Err(e) => Err(e), }}fn main(){ let read = read_file(); println!("{:?}", read)}
hello.txt
中寫(xiě)一段話,則會(huì)被打印出來(lái)。刪除hello.txt
文件,則打印的是錯(cuò)誤信息。
通過(guò)運(yùn)算符?
簡(jiǎn)寫(xiě),處理錯(cuò)誤信息,替代match
的返回錯(cuò)誤信息。
fn read_file() -> Result { let mut file_name = File::open("hello.txt")?; let mut name = String::new(); file_name.read_to_string(&mut name)?; Ok(name)}
使用?
簡(jiǎn)化了很多代碼,還可以通過(guò)鏈?zhǔn)秸{(diào)用操作,使代碼更為簡(jiǎn)短。
fn read_file() -> Result { let mut name = String::new(); File::open("hello.txt")?.read_to_string(&mut name)?; Ok(name)}
rust 還提供了更為方便的寫(xiě)法,fs::read_to_string
,它做了這些事情:打開(kāi)文件、新建一個(gè) String、讀取文件的內(nèi)容。將內(nèi)容放入 String,并返回它。
fn read_file() -> Result { fs::read_to_string("hello.txt")}
?
運(yùn)算符只能用在返回值為Result
類型的方法中。
panic!
在更多的情況,我們都希望程序不要中斷執(zhí)行,所以處理結(jié)果返回Result
類型是最好的選擇。
還有一些,希望不執(zhí)行的情況。
panic!
panic!
代表了無(wú)法處理的錯(cuò)誤。停止執(zhí)行以防止代碼繼續(xù)執(zhí)行出現(xiàn)的不可預(yù)估的錯(cuò)誤。
關(guān)鍵詞:
Copyright 2015-2022 歐洲城建網(wǎng) 版權(quán)所有 備案號(hào):滬ICP備2022005074號(hào)-23 聯(lián)系郵箱: 58 55 97 3@qq.com