【问题标题】:Should I avoid unwrap in production application?我应该避免在生产应用程序中打开包装吗?
【发布时间】:2016-09-13 19:33:01
【问题描述】:

unwrap 在运行时很容易崩溃:

fn main() {
    c().unwrap();
}

fn c() -> Option<i64> {
    None
}

结果:

   Compiling playground v0.0.1 (file:///playground)
 Running `target/debug/playground`
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', ../src/libcore/option.rs:325
note: Run with `RUST_BACKTRACE=1` for a backtrace.
error: Process didn't exit successfully: `target/debug/playground` (exit code: 101)

unwrap 是否仅用于快速测试和概念验证?

如果我真的想在运行时避免panic!,我不能肯定“我的程序不会在这里崩溃,所以我可以使用unwrap”,我认为避免panic!是我们在生产应用程序中想要的.

换句话说,如果我使用unwrap,我可以说我的程序是可靠的吗?还是我必须避免unwrap,即使情况看起来很简单?

我看了this 回答:

最好在您确定没有错误时使用。

但我认为我不能“肯定”。

我不认为这是一个意见问题,而是一个关于 Rust 核心和编程的问题。

【问题讨论】:

  • Crash 是当今国际海事组织严重误用的词;人们甚至将它用于带有异常的语言中的未处理异常。例如,恐慌程序远没有段错误那么糟糕。恐慌是故意说“程序的状态是错误,我现在停止,没有两种方法。
  • @Shepmaster 好的,感谢您的精确!

标签: error-handling rust


【解决方案1】:

虽然整个“错误处理”主题非常复杂且通常基于意见,但这个问题实际上可以在这里得到回答,因为 Rust 的哲学相当狭窄。那就是:

  • panic! 编程错误(“错误”)
  • 使用Result&lt;T, E&gt;Option&lt;T&gt; 正确传播和处理预期和可恢复错误

可以将unwrap() 视为这两种错误之间的转换(它将可恢复的错误转换为panic!())。当您在程序中写unwrap() 时,您是在说:

此时,None/Err(_) 值是一个编程错误,程序无法从中恢复。


例如,假设您正在使用 HashMap 并想要插入一个您可能希望稍后改变的值:

age_map.insert("peter", 21);
// ...

if /* some condition */ {
    *age_map.get_mut("peter").unwrap() += 1;
}

这里我们使用unwrap(),因为我们可以确定键包含一个值。如果没有,这将是一个编程错误,更重要的是:它不是真正可恢复的。如果此时键 "peter" 没有任何价值,你会怎么做?尝试重新插入...?

但您可能知道,Rust 标准库中有一个漂亮的 entry API 用于映射。使用该 API,您可以避免所有这些 unwrap()s。这几乎适用于所有情况:您可以经常重组代码以避免unwrap()!只有在极少数情况下,没有办法绕过它。但是用它就可以了,如果你想发出信号:在这一点上,这将是一个编程错误。


最近有一篇关于“错误处理”主题的相当受欢迎的博客文章,其结论类似于 Rust 的哲学。它相当长但值得一读:“The Error Model”。以下是我尝试总结与此问题相关的文章:

  • 故意区分编程错误可恢复的错误
  • 使用“快速失败”方法来编写错误

总结:当您确定您收到的可恢复错误实际上是不可恢复时,请使用unwrap()。在受影响行上方的评论中解释 “为什么?” 的奖励积分 ;-)

【讨论】:

  • 轰,超清晰!我仔细阅读并学到了很多东西!我会记住这一点:“你可以经常重组你的代码来避免 unwrap()!”
  • 如果只提到expect(),我会 +1 这个答案。
  • 我宁愿他用expect("") 说明他在字符串中的期望,而不是unwrap() 在上面的评论中说明期望!
  • 我不确定。当然,expect("") 通常是更好的选择。但是:无论如何,用户都不应该看到恐慌。如果堆栈跟踪已经足以让开发人员找出问题所在,那么只需unwrap() 就足够了。另外:我说的是 4 到 15 行 cmets,准确描述了为什么以下 unwrap() 不可能失败。我不想将它输入到字符串中。
  • 比起.unwrap(),我更喜欢.expect("string literal is valid regex") 之类的东西,因为它有助于记录为什么 最初选择了展开,这对于在多个循环时决定正确的修复很重要维护和重构掩盖了它。 (更不用说可能通知重构,因此引入问题的几率更低。)
【解决方案2】:

换句话说,如果我使用 unwrap,我可以说我的程序是可靠的吗?或者即使案件看起来很简单,我也必须避免打开包装?

我认为明智地使用unwrap 是你必须学会​​处理的事情,它不能只是避免。

我的反问弹幕是:

  1. 如果我对向量、数组或切片使用索引,我能说我的程序是可靠的吗?
  2. 如果我使用整数除法,我能说我的程序可靠吗?
  3. 如果我添加数字,我可以说我的程序可靠吗?

(1) 就像展开,如果您违反合同并尝试索引超出范围,则会出现索引恐慌。这将是程序中的一个错误,但它并没有像调用 unwrap 那样受到关注。

(2) 类似于展开,如果除数为零,整数除法会出现恐慌。

(3) 与 unwrap 不同,添加不会检查发布版本中的溢出,因此它可能会默默地导致回绕和逻辑错误。

当然,有一些策略可以处理所有这些,而不会在代码中留下恐慌情况,但是许多程序只是简单地使用例如边界检查。

【讨论】:

    【解决方案3】:

    这里有两个问题合二为一:

    • 在生产中使用panic! 是否可接受
    • 在生产中使用unwrap 是否可接受

    panic! 是在 Rust 中用于指示不可恢复的情况/违反假设的工具。它可用于使程序在遇到此故障时无法继续运行(例如,OOM 情况)或在知道它无法执行的情况下绕过编译器(目前)。

    unwrap 是一种方便,最好在生产中避免使用。关于unwrap 的问题在于它没有说明违反了哪个假设,最好使用expect(""),它在功能上是等效的,但也可以提供关于哪里出了问题的线索(不打开源代码)。

    【讨论】:

      【解决方案4】:

      unwrap() 不一定是危险的。就像unreachable!() 一样,在某些情况下您可以确定某些条件不会被触发。

      返回 OptionResult 的函数有时只适用于更广泛的条件,但由于您的程序的结构,这些情况可能永远不会发生。

      例如:当您从您自己构建的 Vector 创建迭代器时,您知道它的确切长度,并且可以确定在其上调用 next() 多长时间返回一个 Some&lt;T&gt;(您可以安全地使用 @987654328 @它)。

      【讨论】:

      • 我个人认为unwrap() 是危险的,它会阻止你的代码被修改,因为它需要你在编辑代码时非常小心,因为unwrap() 取决于之前写的内容。此外,您可能知道某些unwrap() 可能是安全的条件,但您必须认为这些条件有一天可能会改变,并且您需要重新阅读并理解在此展开之前所做的一切.我还认为,如果您已经写过unwrap(),最好在这些行中发表评论// safe。我这样做是为了检查我对grep 有多少展开。
      • 这些条件将来可能会或可能不会改变。我并不是说这是一个规则——这完全有可能。
      【解决方案5】:

      unwrap 非常适合原型制作,但对于生产来说并不安全。完成初始设计后,您可以返回并将unwrap() 替换为Result&lt;Value, ErrorType&gt;

      【讨论】:

      • unwrap 对于生产来说是完全安全的。未运行的进程处于最安全的状态。
      猜你喜欢
      • 2012-10-14
      • 2018-06-16
      • 2010-12-30
      • 2011-03-26
      • 2014-10-24
      • 1970-01-01
      • 2023-03-13
      • 2011-03-20
      • 2014-10-05
      相关资源
      最近更新 更多