为什么 Rust 需要 `if let` 语法?

18

作为一名来自其他函数式编程语言,并且是Rust新手的人,我对Rust的if let语法的动机有些惊讶。这个RFC提到,如果没有if let,那么"测试和解包一个Option<T>的惯用解决方案"是:

match opt_val {
    Some(x) => {
        do_something_with(x);
    }
    None => {}
}
或者
if opt_val.is_some() {
    let x = opt_val.unwrap();
    do_something_with(x);
}

在Scala中,完全可以采用相同的方法,但惯用解决方案是对Option进行map操作(如果只是为了执行doing_something_with(x)的副作用,也可以使用foreach)。

为什么在Rust中不能采用相同的惯用解决方案呢?

opt_val.map(|x| do_something_with(x));

你如何在Scala中完成这样的事情:if let Some(foo) = bar { ... } else if let Some(foo) = baz { ... } else if let Some(foo) = daz { ... } else { ... }?使用map/flatMap似乎会很嵌套:bar.map(...).orElse(baz.map(...).orElse(daz.map(...))).getOrElse(...) - fantom
例如:List(bar, baz, daz).find(_.isDefined).foreach(println(_)) - bluenote10
是的,如果您需要在每个分支中执行相同的操作,但如果它们不同(每个分支不是println),该怎么办?我最近遇到了这个问题,没有找到比嵌套映射/orElse更好的方法。 - fantom
4个回答

30

map() 旨在将可选值进行 转换,而 if let 则主要用于执行 副作用。虽然 Rust 不是一种纯语言,因此其代码块中可以包含副作用,但 map 语义仍然存在。使用 map() 来执行副作用,虽然可能会混淆您代码的读者,但是不应该具有性能惩罚,至少在简单的代码中 - LLVM 优化器完全能够将闭包直接内联到调用函数中,因此它变成等效于一个 match 语句。

在引入 if let 之前,在 Option 上执行副作用的唯一方式是通过匹配或带有 Option::is_some() 检查的 if。match 方法是最安全的方法,但是它非常冗长,尤其是需要大量嵌套检查时:

match o1 {
    Some(v1) => match v1.f {
        Some(v2) => match some_function(v2) {
            Some(r) => ...
            None => {}
        }
        None => {}
    }
    None => {}
}

注意到明显的向右偏移和大量语法噪音。如果分支不是简单匹配而是具有多个语句的适当块,则情况只会变得更糟。

if option.is_some() 方法虽然略微简洁,但仍然读起来非常糟糕。此外,它的条件检查和 unwrap() 不是静态绑定的,因此有可能出错,而编译器却不会注意到。

if let 解决了冗长的问题,它基于与 match 相同的模式匹配基础设施(因此比 if option.is_some() 更难出错),并且作为一个附带好处,允许在模式中使用任意类型,而不仅仅是 Option 。例如,某些类型可能不提供类似于 map() 的方法;if let 仍将非常好地处理它们。 因此,if let 是一种清晰胜出的选择,因此是惯用的。


1
在编写代码时,使用map执行副作用只会让读者感到迷惑。这就是为什么Scala总是提供两个版本的map,一个用于转换,另一个用于副作用。我喜欢Scala方法的原因是,它在不引入额外语法的情况下解决了同样的问题。预计Scala用户会经常对Options进行map操作:)(在Scala中是无处不在的)。 - bluenote10
2
@bluenote10,我以编写Scala为生,所以我非常清楚这一点 :) 再次注意,if let不仅适用于Option,而且语法比使用闭包更加轻量级。 - Vladimir Matveev
在这里是否应该提到,如果map导致您移出共享引用,那么相比于if letmap可能不太理想,因为它需要额外的as_ref()?这至少是我在Rust中很少使用map的原因之一,占了50%。 - ecoe

18

.map() 方法是特定于 Option<T> 类型的,但 if let(以及 while let!)是适用于 Rust 所有类型的特性。


2
注意:我接受这个答案,因为它简要概述了主要的技术差异,但请参考@Vladimir的答案以获得更全面的解释。 - bluenote10

4

以下是与《Rust编程语言》相关问题的引用,来自相关问题

你最好把 if let 看作是一个单手无需强制进行详尽检查的 match。它通常与 iflet 没有任何关系,这就是为什么它如此令人困惑的原因。 :)

也许更好的做法是将整个运算符重命名,称之为 match once 或类似的名称。

(稍加修改,使其脱离上下文即可理解)


2
“if let” 的语法也很令人困惑,因为它将 rvalue 放在 lvalue 左边。 - hippietrail

3
因为你的解决方案创建了一个闭包,使用了资源,而if let则精确地简化为你的第一个示例,不会产生这种情况。我认为它更易读。
Rust专注于零成本抽象,使编程更加美好,if letwhile let就是这样的良好示例(至少在我看来如此 - 我意识到这是个人偏好问题)。它们并不是严格必要的,但使用起来感觉非常好(也可以参见:Clojure,它们很可能是从那里提取的)。

1
据我所知,它们是从Swift中提取的。 - Shepmaster
换句话说,如果不需要考虑性能问题,那么映射是可以的吗?考虑到 if let 语法和 map 在语法上几乎相同,我想知道优化器是否实际上可以避免首先创建闭包... - bluenote10
6
有了新的闭包,使用 map 函数实际上和使用 match 语句是一样快的,而且很可能编译成完全相同的代码,因为闭包是静态调用的,可以轻松地内联。 - reem
3
强调一下:.map()也是一个零成本的抽象。 - bluss

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