在这篇文章中,我将介绍Option和Result这两种枚举类型。
Option
Rust版本的空类型是Option<T>类型,它是一种枚举类型,其中每个实例要么是:
None
Some(value)
在这里,value可以是任何T类型的值。例如,Vec<T>有一个pop()方法,返回一个Option<T>,如果vector为空,该方法将返回None。
返回Option的API的好处之一是,要获得其中的值,编译器强制调用者必须检查值是否为None。在c++中,std::find()返回一个迭代器,但是必须记得检查它以确保它不是容器的end()——如果忘记了这个检查并试图将值从容器中取出,将得到未定义的行为。
缺点是,这会使代码变得冗长,但是,Rust有很多方法可以简化!
使用expect和unwrap
如果你确定一个Option里面有一个真正的值,那么expect()和unwrap()就是为你准备的!它们返回内部的值,但如果变量实际上是None,程序将退出。(这就是panic,在某些情况下,panic是可以恢复的,但为了简单起见,这里我们将忽略这一点。)
惟一的区别是expect()允许你指定一个自定义消息,该消息在程序退出时打印到控制台。
还有一个unwrap_or(),它允许在值为None时指定默认值,因此Some(5).unwrap_or(7)的值为5,None.unwrap_or(7)的值为7。
如果你愿意,你可以在调用unwrap()之前检查Option<T>是否有一个值,像这样:
// t is an Option<T>
if t.is_some() {
let real_value = t.unwrap();
}
但是,还有更简洁的方法(例如,使用if let,我们稍后会介绍)。
使用match
查看Option是否有值的最基本方法是使用匹配表达式进行模式匹配。这适用于任何枚举类型,看起来像这样:
// t is an Option<T>
match t {
None => println!("No value here!"), // one match arm
Some(x) => println!("Got value {}", x) // the other match arm
};
需要注意的一件事是,Rust编译器强制匹配表达式必须是穷尽的;也就是说,每个可能的值都必须被匹配。因此,下面的代码无法编译:
// t is an Option<T>
match t {
Some(x) => println!("Got value {}", x)
};
我们得到一个错误:
error[E0004]: non-exhaustive patterns: `None` not covered
这实际上是非常有用的,以避免你认为涵盖了所有的情况。但是,如果你显式地想忽略所有其他情况,你可以使用_ match表达式:
// t is an Option<T>
match t {
Some(x) => println!("Got value {}", x), // the other match arm
_ => println!("OK not handling this case.");
};
使用if let
只有当Option有一个实际有用的值时,可以使用if let这种简洁的方法。例如,如果t有一个值,下面的代码将打印"Got <value>",如果t是None则不做任何操作:
// t is an Option<T>
if let Some(i) = t {
println!("Got {}", i);
}
使用map
还有很多方法可以对Option<T>执行操作,而无需检查它是否有值。例如,如果有实际值,可以使用map()转换,否则将其保留为None。例如:
Some(10).map(|i| i + 1) == Some(11)
None.map(|i| i + 1) == None
使用into_iter
Vec<Option<T>>,可以将其转换为Option<Vec<T>>,如果原始Vector中的任何项为None,则该值为None。
如果考虑从多个操作接收结果,并且希望在任何单个操作失败时整个结果失败,那么这是有意义的。例如:
vec![Some(10), Some(20)].into_iter().collect() == Some([10, 20])
vec![Some(10), Some(20), None].into_iter().collect() == None
Result
Rust的Result<T, E>类型是一种返回值或错误的枚举类型。与Option类型类似,有两种可能的变体:
Ok(T):表示操作成功,值为T。
Err(E):意味着操作失败,错误E。
使用ok_or
由于Option和Result是如此相似,有一个简单的方法在两者之间转换。
Option的方法:
Some(10).ok_or("uh-oh") == Ok(10)
None.ok_or("uh-oh") == Err("uh-oh")
Result的方法:
Ok(10).ok() == Some(10)
Err("uh-oh").ok() == None
Result上还有一个err()方法,其作用与此相反:errors被映射为Some,success值被映射为None。
使用expect, unwrap, match, and if let
就像使用Option一样,Result的expect()和unwrap()的工作方式与使用Option完全相同。而且,由于Result是一个枚举类型,match和if let也以相同的方式工作!
使用"?"操作符
很多时候,如果一个函数返回错误,你希望直接从调用函数中返回这个错误。所以,你的代码看起来像下面这样:
let inner_result = other_function();
if let Err(some_error) = inner_result {
return inner_result;
}
let real_result = inner_result().unwrap();
// now real_result has the actual value we care about, we can continue on...
但是,一遍又一遍地返回错误是一种痛苦。相反,你可以这样写代码:
let real_result = other_function()?;
更好的是,您可以将调用链接在一起,像这样:
let real_result = this_might_fail()?.also_might_fail()?.this_one_might_fail_too()?;
另一种常见的技术是使用类似map_err()的方法将错误转换为符合外部函数返回的结果,然后使用?操作符。
使用into_iter
类似于Option,如果你有Vec<Result<T, E>>,你可以使用into_iter()和collect()将其转换为Result<Vec<T>, E>。例如:
vec![Ok(10), Ok(20)].into_iter().collect() == Ok([10, 20])
vec![Ok(10), Err("bad"), Ok(20), Err("also bad")].into_iter().collect() == Err("bad")
本文翻译自:
https://blog.logrocket.com/understanding-rust-option-results-enums/