Rust中是否有一种更简洁的方法从Option中提取值?

10

我发现自己经常会做类似以下的事情:

fn foo() -> Result<i32, String> {
    let cur = match something_that_returns_an_option() {
        Some(cur) => cur,
        None => return Err("Some error"),
    };
    
    // use `cur`
    
    1
}

如果我需要多个变量,我就会一遍又一遍地使用这种模式,或者嵌套使用 if/let 或 match 语句。

有没有更加人性化的方式来处理反复从选项中提取值的情况?


请参阅 Rust 书中的 传播错误的快捷方式:? 运算符 - Charles Duffy
如果函数返回一个 Result,那么这并不能解决问题。我更新了我的问题以使这更加明显。 - marcantonio
那个编辑很有帮助。你可能需要写宏来解决问题,但我是一个相对新手,所以可能还有别的方法我不知道。 - Charles Duffy
2个回答

11
您正在寻找Option::ok_or。它可以让您将一个Option映射为一个带有提供的错误信息的Result。结合?运算符,您可以优雅地整理代码:
fn foo() -> Result<i32, String> {
    let cur = something_that_returns_an_option().ok_or("some error")?;
        
    Ok(cur + 1)
}

游乐场

Option::ok_or_else可能也很有用,因为它会惰性地评估错误分支。


非常干净。谢谢!如果我想在None情况下返回Ok(1)怎么办? - marcantonio
@marcantonio 我认为 Rust 没有一个很好的方法来做到这一点,尽管根据你想要做什么,Option::unwrap_or 可能会引起兴趣。 - Aiden4
1
@marcantonio:如果你想要自定义逻辑,那么 let cur = if let Some(v) = something_that_returns_an_option() { v } else { return Ok(1) }; 目前是稳定版本中最短的选项了,恐怕没有更短的了。如上所述,在 Nightly 版本中有 let Some(cur) = something_that_returns_an_option() else { return Ok(1) }; 这个更短的选项。 - Matthieu M.

6

在您的示例中,您不仅想要继续、跳出或返回常规值,而是返回一个错误。对于这种特殊情况,Aiden4的答案是正确的选择。或者,如果您正在使用 anyhow,您应该查看 this

但我曾经遇到过这样的情况,我想要取消包装或(在 None 的情况下)直接 continuebreakreturn 非错误值。

更新(Rust 1.65+)

let-else

let-else 功能已在 Rust 1.65 中稳定。以下是如何使用它的示例:

let options = vec![Some(74), None, Some(9)];
for o in options {
    let Some(v) = o else { continue };
    println!("v = {v}");
}

旧的答案(和备选方案)

Rust(仍然)没有提供一种简短而明确地完成这件事的方法。

这里有一个“一行代码”的方法,可以实现该功能,但是仍然有点冗长:

let v = if let Some(d) = some_option_value { d } else { continue; };

如果您想要一个更短的解决方案,这里有两个选项...

您可以编写如下的宏:
macro_rules! unwrap_or {
    ($e:expr, $or_do_what:expr) => {
        if let Some(d) = $e { d } else { $or_do_what }
    };
}

这将允许您编写如下代码:

let options = vec![Some(74), None, Some(9)];
for o in options {
    let v = unwrap_or!(o, continue);
    // ...
}

这只是一个琐碎的例子,但我认为最大的好处在于当您需要执行多个检查时,可以避免编写类似于这样的“惯用语”:

for thing in things {
    if let Some(a) = thing {
        // ...
        if let Some(b) = a.another_opt {
            // ...
            if let Some(c) = a.yet_another_opt {
                // ...
            }
        }
    }
}

您可以通过避免嵌套多个代码块来简化代码,例如:

for thing in things {
    let a = unwrap_or!(thing, continue);
    // ...
    let b = unwrap_or!(a.another_opt, continue);
    // ...
    let c = unwrap_or!(a.yet_another_opt, continue);
    // ...
}

这是否是一个好的实践,当然是主观的。

let...else(不稳定)

有一个不稳定的let...else功能,旨在解决这个问题。例如:

#![feature(let_else)]
...
let options = vec![Some(74), None, Some(9)];
for o in options {
    let Some(v) = o else { continue };
    println!("v = {v}");
}

请注意,“一行代码”片段已由默认设置的rustfmt重新格式化为不少于5行。:( - user4815162342
@user4815162342 很好的观点。在这方面,我使用了更少严格的IDE设置,但即便如此,“一行代码”也不如我希望的那样易于阅读。 - at54321
谢谢,这很好。需要在这里使用宏是我对 Rust 感到沮丧的原因,让我感觉自己缺少了一些基本的东西。 - marcantonio
1
@marcantonio 你没有错过任何东西- 这种错误处理的痛点已经存在一段时间了。它导致了 ? 操作符,并最终将引导到 try 块 - Aiden4
1
@marcantonio,有一个RFC正在制定将其内置:https://rust-lang.github.io/rfcs/3137-let-else.html。 - GManNickG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接