有什么好的情况下我们应该使用`unwrap`吗?

时间:2018-12-25 10:07:33

标签: rust unwrap

由于使用unwrap可能会出现问题,因为它会在错误情况下崩溃,因此可能被视为危险用法。

如果我百分百确定它不会崩溃,该怎么办?

if option.is_some() {
    let value = option.unwrap();
}
if result.is_ok() {
    let result_value = result.unwrap();
}

由于我们已经检查了ResultOption,因此unwrap()的使用不会崩溃。但是,我们可以使用matchif let。我认为matchif let的用法都比较优雅。

2 个答案:

答案 0 :(得分:4)

让我们专注于Result;最后,我将回到Option

Result的目的是发出可能因错误而成功或失败的结果的信号。因此,对它的任何使用都应属于此类别。让我们忽略箱子返回Result进行无法执行的操作的情况。

通过做您正在做的事情(检查if result.is_ok() 然后提取值),实际上您在做同一件事两次。第一次,您正在检查Result的内容,第二次,您正在不安全地检查和提取。

这确实可以用matchmap完成,并且两者都比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;
    }
}

但是,此实现既笨拙又效率低下。我们可以通过完全避免使用unwrapif语句来简化此操作。

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很相似,并且支持相同的操作。如果您要处理OptionResult的内容并进行相应的分支,而您正在使用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表示满意。一丝不确定,我就诉诸ifmatchmap等。

请注意,还有expect,它基本上是“ unwrap,在错误情况下带有注释”。但是,我发现这不是真正的人体工程学。此外,如果unwrap失败,我发现回溯有点难以阅读。因此,我目前使用的宏verify!的唯一自变量是OptionResult,并检查该值是否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(),以便可以快速在我的代码库中搜索发生错误的位置。 (根据我的经验,这比通过回溯查找错误的根源要快。)