學習rust的朋友可能經常看到Result和Option,雖然不一定直接看到他們本身,但是他們的方法還是常見的,比如:xxx.ok().expect(“…”);
這個xxx一般就是某個函數返回的Result類型了,下面就詳細的講解下他們的來源
現在看看rust book裏的那個guess game,有這麼一段:
http://doc.rust-lang.org/book/guessing-game.html
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.ok()
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
前面幾行都好理解,我們看看10~12行的幾個ok,expect到底是什麼
理解這些內容最好的方法就是看源代碼,不過大家別慌,不是要你從頭開始啃rust所有源碼,只需要有針對性的看就可以了
大家記住下面這個常用的鏈接,這兒可以查到rust的所有源代碼,可以搜索,反應速度非常快:
https://doc.rust-lang.org/std/
1、io::stdin()
現在從頭開始,先看io::stdin()這個是什麼
這個網頁的最上方就是輸入框了,我們一步步來,先輸入 io::stdin
注意:不要加後面括號
下面就是搜索的結果,可以看到第二行就是我們要找的函數(第一行是結構體,我們要找的是函數)
點擊進去就可以查看源代碼了,這個函數聲明很簡單,只是返回了一個Stdin的結構體
如果想看源代碼就點右上角的那個src,見上圖右上角紅色框
我們現在不需要去看源代碼了,現在看看Stdin的介紹就可以了,看圖上左邊的紅色框裏的Stdin是可以點擊的,點進去然後找到read_line方法:
fn read_line(&mut self, buf: &mut String) -> Result<usize>[−]
Locks this handle and reads a line of input into the specified buffer.
For detailed semantics of this method, see the documentation on BufRead::read_line.
上面是read_line的介紹,我們不去關心他的實現過程了,先看看他返回的類型是:
Result<usize>
2、Result
點進去看看Result的頁面,這個就是這篇blog的重點了
type Result<T> = Result<T, Error>;
A type for results generated by I/O related functions where the Err type is hard-wired to io::Error.
This typedef is generally used to avoid writing out io::Error directly and is otherwise a direct mapping to std::result::Result.
上面的介紹部分說的是io::Result其實是爲了書寫方便定義的,他用io::Error類型替代了std::result::Result<T,Error>
裏的Error類型
這樣io::Result比std::result::Result更加具體化了,那麼寫起來也相對簡單了,他只可能返回io::Error類型的錯誤
因爲這兒io::Result只是個類型定義,所以我們要去看std::result::Result的源代碼,搜索過程就不詳述了,具體看源碼:
pub enum Result<T, E> {
/// Contains the success value
#[stable(feature = "rust1", since = "1.0.0")]
Ok(T),
/// Contains the error value
#[stable(feature = "rust1", since = "1.0.0")]
Err(E)
}
上面就是定義了,可以看到他是個enum,有OK和Err類型,分別對應了Result泛型裏的類型T和E,std::result::Result裏並沒有限制E和T的類型,但是io::Result就把E的類型限制成了io::Error,這個大家注意下就好
說了這麼多我們再看看問題
io::stdin().read_line(&mut guess)
.ok()
.expect("Failed to read line");
剛纔我們確認了read_line返回的是io::Result<T>
類型,那麼ok()函數肯定就是Result的一個方法了,繼續看std::result::Result的方法實現的源代碼:
pub fn ok(self) -> Option<T> {
match self {
Ok(x) => Some(x),
Err(_) => None,
}
}
3、Option
可以看到ok函數返回的是Option<T>
,還是沒有返回我們最終想要的類型T,我們還是先看看ok的代碼吧。
其實這個函數非常簡單,就是一個match,如果沒有出錯用Option::Some把我們要的數據用包裝下返回;如果出錯了就返回Option::None
這樣皮球又提到了Option去了…我們再繼續查Option:
pub enum Option<T> {
None,
Some(T),
}
上面就是Option的定義,也是個enum。其中None顧名思義就是“沒有”,他沒有包裝類型T,所以他真的什麼都沒。Some帶來我們的類型T,所以現在目標已經很靜了,只要把T對應的數據弄出來就最終得到了我們要的數據了
繼續看這個代碼,ok()返回的是Option,那麼expect肯定就是Option的方法了
io::stdin().read_line(&mut guess)
.ok()
.expect("Failed to read line");
繼續看Option源代碼,看他方法的實現:
pub fn expect(self, msg: &str) -> T {
match self {
Some(val) => val,
None => panic!("{}", msg),
}
}
終於看到想要的返回類型了:T,這個就是我們最終要的數據了,看read_line返回的是io::Result<usize>
,所以這兒返回的是一個uszie類型的長度,不過guess game裏並沒有使用他
可以看到這個函數也是個match,如果是Some能匹配了就把他攜帶的數據返回;如果是None類型說明這個Option根本就沒攜帶數據,也就是前面的Result出錯了,所以會調用panic!宏把你傳遞進去的字符串打印出來並且退出程序。
4、其他寫法
現在繞了一大圈終於找到T了,其實ok().expect(…);這種只是偷懶的寫法,出錯了直接打印你的字符串就退出程序了。當然有更偷懶的,看std::result::Result的代碼:
pub fn unwrap(self) -> T {
match self {
Ok(t) => t,
Err(e) =>
panic!("called `Result::unwrap()` on an `Err` value: {:?}", e)
}
}
那麼代碼就可以寫成下面這樣就可以了:
io::stdin().read_line(&mut guess).unwrap();
這個是非常不建議的用法,除非你非常肯定read_line不可能出錯
雖然這個read_line結果我們並沒有使用,但是還是需要處理一下,不然Result沒有處理編譯器會給警告的
處理result最直接和直觀的方法就是直接match,不需要通過Option中轉了:
match io::stdin().read_line(&mut guess) {
Ok(size)=>xxxx,
Err(e)=>xxx,
}
xxx處換成你自己的代碼就可以了