由于使用unwrap
可能会出现问题,因为它会在错误情况下崩溃,因此可能被视为危险用法。
如果我百分百确定它不会崩溃,该怎么办?
if option.is_some() {
let value = option.unwrap();
}
if result.is_ok() {
let result_value = result.unwrap();
}
由于我们已经检查了Result
和Option
,因此unwrap()
的使用不会崩溃。但是,我们可以使用match
或if let
。我认为match
或if let
的用法都比较优雅。
答案 0 :(得分:4)
让我们专注于Result
;最后,我将回到Option
。
Result
的目的是发出可能因错误而成功或失败的结果的信号。因此,对它的任何使用都应属于此类别。让我们忽略箱子返回Result
进行无法执行的操作的情况。
通过做您正在做的事情(检查if result.is_ok()
然后提取值),实际上您在做同一件事两次。第一次,您正在检查Result
的内容,第二次,您正在不安全地检查和提取。
这确实可以用match
或map
完成,并且两者都比if
更惯用。考虑一下这种情况:
您有一个实现以下特征的对象:
use std::io::{Error, ErrorKind};
trait Worker {
fn hours_left(&self) -> Result<u8, Error>;
fn allocate_hours(&mut self, hours: u8) -> Result<u8, Error>;
}
我们将假设hours_left()
确实按照锡罐上的规定行事。我们还假设我们有Worker
的可变借项。让我们实现allocate_hours()
。
为此,我们显然需要检查我们的工作人员是否还有多余的时间来分配。您可以将其写成与您的相似:
fn allocate_hours(&mut self, hours: u8) {
let hours_left = self.hours_left();
if (hours_left.is_ok()) {
let remaining_hours = hours_left.unwrap();
if (remaining_hours < hours) {
return Err(Error::new(ErrorKind::NotFound, "Not enough hours left"));
}
// Do the actual operation and return
} else {
return hours_left;
}
}
但是,此实现既笨拙又效率低下。我们可以通过完全避免使用unwrap
和if
语句来简化此操作。
fn allocate_hours(&mut self, hours: u8) -> Result<u8, Error> {
self.hours_left()
.and_then(|hours_left| {
// We are certain that our worker is actually there to receive hours
// but we are not sure if he has enough hours. Check.
match hours_left {
x if x >= hours => Ok(x),
_ => Err(Error::new(ErrorKind::NotFound, "Not enough hours")),
}
})
.map(|hours_left| {
// At this point we are sure the worker has enough hours.
// Do the operations
})
}
我们在这里用一块石头杀死了多只鸟。我们使代码更具可读性,更易于遵循,并且删除了大量的重复操作。这也开始看起来像Rust,而不像PHP;-)
Option
很相似,并且支持相同的操作。如果您要处理Option
或Result
的内容并进行相应的分支,而您正在使用unwrap
,那么一旦忘记了,您将不可避免地陷入很多陷阱打开东西。
在某些真正的情况下,您的程序应该退出。对于这些,请考虑使用expect(&str)
而不是unwrap()
答案 1 :(得分:2)
在许多情况下,您可以通过更优雅的方式避免使用unwrap
和其他情况。但是,我认为在某些情况下它是unwrap
的正确解决方案。
例如,Iterator
中的许多方法都返回Option
。让我们假设您有一个非空切片(被不变式称为非空),并且想要获得最大值,可以执行以下操作:
assert!(!slice.empty()); // known to be nonempty by invariants
do_stuff_with_maximum(slice.iter().max().unwrap());
对此可能有几种看法,但我认为在上述情况下使用unwrap
完全可以-在前面的assert!
存在的情况下。
我的指导原则是:如果要处理的参数全部来自我自己的代码,而不与第三方代码进行交互,可能assert!
不变量,那么我对unwrap
表示满意。一丝不确定,我就诉诸if
,match
,map
等。
请注意,还有expect
,它基本上是“ unwrap
,在错误情况下带有注释”。但是,我发现这不是真正的人体工程学。此外,如果unwrap
失败,我发现回溯有点难以阅读。因此,我目前使用的宏verify!
的唯一自变量是Option
或Result
,并检查该值是否unwrap
可用。它是这样实现的:
pub trait TVerifiableByVerifyMacro {
fn is_verify_true(&self) -> bool;
}
impl<T> TVerifiableByVerifyMacro for Option<T> {
fn is_verify_true(&self) -> bool {
self.is_some()
}
}
impl<TOk, TErr> TVerifiableByVerifyMacro for Result<TOk, TErr> {
fn is_verify_true(&self) -> bool {
self.is_ok()
}
}
macro_rules! verify {($e: expr) => {{
let e = $e;
assert!(e.is_verify_true(), "verify!({}): {:?}", stringify!($e), e)
e
}}}
使用此宏,上述示例可以写为:
assert!(!slice.empty()); // known to be nonempty by invariants
do_stuff_with_maximum(verify!(slice.iter().max()).unwrap());
如果我不能unwrap
的值,则会收到一条错误消息,提示slice.iter().max()
,以便可以快速在我的代码库中搜索发生错误的位置。 (根据我的经验,这比通过回溯查找错误的根源要快。)