学习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处换成你自己的代码就可以了