rust一些習慣表達方法

學習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處換成你自己的程式碼就可以了