github地址:https://github.com/bradyjoestar/rustnotes(歡迎star!)
pdf下載鏈接:https://github.com/bradyjoestar/rustnotes/blob/master/Rust%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.pdf
參考:
https://rustcc.gitbooks.io/rustprimer/content/ 《RustPrimer》
https://kaisery.github.io/trpl-zh-cn/ 《Rust程序設計語言-簡體中文版》
第十章 Rust語法補充
10.1 Result與錯誤處理
大部分錯誤并沒有嚴重到需要程序完全停止執行。有時,一個函數會因為一個容易理解并做出反應的原因失敗。例如,如果嘗試打開一個文件不過由于文件并不存在而失敗,此時我們可能想要創建這個文件而不是終止進程。
在Rust中,提供了 Result 枚舉,它定義有如下兩個成員,Ok 和 Err:
enum Result<T, E> {
Ok(T),
Err(E),
}
T 和 E 是泛型類型參數;T代表成功時返回的 Ok 成員中的數據的類型,而 E 代表失敗時返回的 Err 成員中的錯誤的類型。(T,E這也是Rust中的枚舉不同于其它語言枚舉的地方)。
因為 Result 有這些泛型類型參數,我們可以將 Result 類型和標準庫中為其定義的函數用于很多不同的場景,這些情況中需要返回的成功值和失敗值可能會各不相同。
在下面的例子中,我們增加根據 File::open 返回值進行不同處理的邏輯。
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("There was a problem opening the file: {:?}", error)
},
};
}
注意與 Option 枚舉一樣,Result 枚舉和其成員也被導入到了 prelude 中,所以就不需要在 match 分支中的 Ok 和 Err 之前指定 Result::。
這里我們告訴 Rust 當結果是 Ok 時,返回 Ok 成員中的 file 值,然后將這個文件句柄賦值給變量 f。match 之后,我們可以利用這個文件句柄來進行讀寫。
match 的另一個分支處理從 File::open 得到 Err 值的情況。在這種情況下,我們選擇調用 panic!宏。如果當前目錄沒有一個叫做 hello.txt 的文件,當運行這段代碼時會看到如下來自 panic! 宏的輸出。
10.1.1 匹配不同的錯誤
上面的例子中代碼不管 File::open 是因為什么原因失敗都會 panic!。我們真正希望的是對不同的錯誤原因采取不同的行為:如果 File::open因為文件不存在而失敗,我們希望創建這個文件并返回新文件的句柄。
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Tried to create file but there was a problem: {:?}", e),
},
other_error => panic!("There was a problem opening the file: {:?}", other_error),
},
};
}
File::open 返回的 Err 成員中的值類型 io::Error,它是一個標準庫中提供的結構體。這個結構體有一個返回 io::ErrorKind 值的 kind 方法可供調用。io::ErrorKind 是一個標準庫提供的枚舉,它的成員對應 io 操作可能導致的不同錯誤類型。我們感興趣的成員是 ErrorKind::NotFound,它代表嘗試打開的文件并不存在。所以 match 的 f 匹配,不過對于 error.kind() 還有一個內部 match。
我們希望在匹配守衛中檢查的條件是 error.kind() 的返回值是 ErrorKind的 NotFound 成員。如果是,則嘗試通過 File::create 創建文件。然而因為 File::create 也可能會失敗,還需要增加一個內部 match 語句。當文件不能被打開,會打印出一個不同的錯誤信息。外部 match 的最后一個分支保持不變這樣對任何除了文件不存在的錯誤會使程序 panic。
Result<T, E> 有很多接受閉包的方法,并采用 match 表達式實現。一個更老練的 Rustacean 可能會這么寫:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt").map_err(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Tried to create file but there was a problem: {:?}", error);
})
} else {
panic!("There was a problem opening the file: {:?}", error);
}
});
}
10.1.2 unwrap與expect
match 能夠勝任它的工作,不過它可能有點冗長并且不總是能很好的表明其意圖。Result<T, E> 類型定義了很多輔助方法來處理各種情況。其中之一叫做 unwrap,如果 Result 值是成員 Ok,unwrap 會返回 Ok 中的值。如果 Result 是成員 Err,unwrap 會為我們調用 panic!。這里是一個實踐 unwrap 的例子:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
還有另一個類似于 unwrap 的方法它還允許我們選擇 panic! 的錯誤信息:expect。使用 expect 而不是 unwrap 并提供一個好的錯誤信息可以表明你的意圖并更易于追蹤 panic 的根源。expect 的語法看起來像這樣:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
expect 與 unwrap 的使用方式一樣:返回文件句柄或調用 panic! 宏。expect 用來調用 panic! 的錯誤信息將會作為參數傳遞給 expect ,而不像unwrap 那樣使用默認的 panic! 信息。
10.1.3 傳播錯誤與傳播錯誤的簡寫
10.1.3.1 傳播錯誤
當編寫一個其實現會調用一些可能會失敗的操作的函數時,除了在這個函數中處理錯誤外,還可以選擇讓調用者知道這個錯誤并決定該如何處理。這被稱為 傳播(propagating)錯誤,這樣能更好的控制代碼調用,因為比起你代碼所擁有的上下文,調用者可能擁有更多信息或邏輯來決定應該如何處理錯誤。
例如,下面的例子中展示了一個從文件中讀取用戶名的函數。如果文件不存在或不能讀取,這個函數會將這些錯誤返回給調用它的代碼:
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.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),
}
}
調用這個函數的代碼最終會得到一個包含用戶名的 Ok 值,或者一個包含 io::Error 的 Err 值。我們無從得知調用者會如何處理這些值。例如,如果他們得到了一個 Err 值,他們可能會選擇 panic! 并使程序崩潰、使用一個默認的用戶名或者從文件之外的地方尋找用戶名。我們沒有足夠的信息知曉調用者具體會如何嘗試,所以將所有的成功或失敗信息向上傳播,讓他們選擇合適的處理方法。
這種傳播錯誤的模式在 Rust 是如此的常見,以至于有一個更簡便的專用語法:?。
10.1.3.2 傳播錯誤的簡寫
下面例子展示了一個 read_username_from_file 的實現,它實現了與上面例子中的代碼相同的功能,不過這個實現使用了問號運算符(運算符重載):
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
File::open 調用結尾的 ? 將會把 Ok 中的值返回給變量 f。如果出現了錯誤,? 會提早返回整個函數并將一些 Err 值傳播給調用者。同理也適用于 read_to_string 調用結尾的 ?。
? 消除了大量樣板代碼并使得函數的實現更簡單。我們甚至可以在 ? 之后直接使用鏈式方法調用來進一步縮短代碼,如下面的例子所示:
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
? 只能被用于返回值類型為 Result 的函數。
下面的例子中:
use std::fs::File;
fn main() {
let f = File::open("hello.txt")?;
}
當編譯這些代碼,會得到如下錯誤信息:
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
--> src/main.rs:4:13
|
4 | let f = File::open("hello.txt")?;
| ^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `std::ops::Try` is not implemented for `()`
= note: required by `std::ops::Try::from_error`
錯誤指出只能在返回 Result 的函數中使用 ?。在不返回 Result 的函數中,當調用其他返回 Result的函數時,需要使用 match 或 Result 的方法之一來處理,而不能用 ? 將潛在的錯誤傳播給代碼調用方。
10.2 Any和反射
use std::any::Any;
use std::fmt::Debug ;
fn load_config<T:Any+Debug>(value: &T) -> Vec<String>{
let mut cfgs: Vec<String>= vec![];
let value = value as &Any;
match value.downcast_ref::<String>() {
Some(cfp) => cfgs.push(cfp.clone()),
None => (),
};
match value.downcast_ref::<Vec<String>>() {
Some(v) => cfgs.extend_from_slice(&v),
None =>(),
}
if cfgs.len() == 0 {
panic!("No Config File");
}
cfgs
}
fn main() {
let cfp = "/etc/wayslog.conf".to_string();
assert_eq!(load_config(&cfp), vec!["/etc/wayslog.conf".to_string()]);
let cfps = vec!["/etc/wayslog.conf".to_string(),
"/etc/wayslog_sec.conf".to_string()];
assert_eq!(load_config(&cfps),
vec!["/etc/wayslog.conf".to_string(),
"/etc/wayslog_sec.conf".to_string()]);
}
熟悉Java的同學肯定對Java的反射能力記憶猶新,同樣的,Rust也提供了運行時反射的能力。但是,這里有點小小的不同,因為 Rust 不帶 VM 不帶 Runtime ,因此,其提供的反射更像是一種編譯時反射。
因為,Rust只能對 'static 生命周期的變量(常量)進行反射!
我們來重點分析一下中間這個函數:
fn load_config<T:Any+Debug>(value: &T) -> Vec<String>{..}
首先,這個函數接收一個泛型T類型,T必須實現了Any和Debug。
這里可能有同學疑問了,你不是說只能反射 'static 生命周期的變量么?我們來看一下Any限制:
pub trait Any: 'static + Reflect {
fn get_type_id(&self) -> TypeId;
}
看,Any在定義的時候就規定了其生命周期,而Reflect是一個Marker,默認所有的Rust類型都會實現他!注意,這里不是所有原生類型,而是所有類型。
好的,繼續,由于我們無法判斷出傳入的參數類型,因此,只能從運行時候反射類型。
let value = value as &Any;
首先,我們需要將傳入的類型轉化成一個 trait Object, 當然了,你高興的話用 UFCS 也是可以做的,參照本章最后的附錄。
這樣,value 就可以被堪稱一個 Any 了。然后,我們通過 downcast_ref 來進行類型推斷。如果類型推斷成功,則 value 就會被轉換成原來的類型。
有的同學看到這里有點懵,為什么你都轉換成 Any 了還要轉回來?
其實,轉換成 Any 是為了有機會獲取到他的類型信息,轉換回來,則是為了去使用這個值本身。
最后,我們對不同的類型處以不同的處理邏輯。最終,一個反射函數就完成了。