为什么`Option`支持`IntoIterator`?

16

我试图迭代字符串向量的一个子部分,即 Vec<String> 的子切片。在每次迭代中,我想把字符串作为 slice 传递给一个函数。

我没有注意到 Vec::get 返回一个 Option,所以错误地想直接迭代返回值:

fn take_str(s: &str) {
    println!("{}", s);
}

fn main() {
    let str_vec: Vec<String> = ["one", "two", "three", "uno", "dos", "tres"]
        .iter()
        .map(|&s| s.into())
        .collect();
    for s in str_vec.get(0..3) {
        take_str(&s);
    }
}
error[E0308]: mismatched types
  --> src/main.rs:11:18
   |
11 |         take_str(&s); // Type mismatch: found type `&&[std::string::String]`
   |                  ^^ expected `str`, found `&[String]`
   |
   = note: expected reference `&str`
              found reference `&&[String]`

我本以为 s 应该是一个 String,但实际上它是一个 &[String]。这是因为我的 for 循环正在迭代由 Vec::get() 返回的 Option

我还编写了以下代码,证明 for 循环实际上正在解包一个 Option

let foo = Option::Some(["foo".to_string()]);
for f in foo {
    take_str(&f); // Same error as above, showing `f` is of type `&[String]`
}

这真是令人困惑;在我编写代码并弄清楚它实际上在做什么之前,我从未想过Option可以通过迭代进行解包。为什么要支持这样的操作?迭代Option有什么用例吗?


4
下面是要翻译的内容:

遍历选项(Option)中的值

当你有一个 Option 类型的值,并想要操作其中包含的值时,你需要先将其解包。但这样做可能会引发一些问题,例如值不存在的情况。更好的方式是使用 iter 方法来遍历 Option 中的值。let option_value: Option = Some(42); for value in option_value.iter() { println!("The value is: {}", value); }option_valueNone 时,上述代码不会产生任何输出。如果你使用 for 循环来遍历 Option 的话,编译器会在代码中加入一些特殊的处理逻辑,以确保只有当值存在时才会执行循环体。如果你希望在值不存在时执行一些特殊的操作,可以使用 if letmatch 表达式来处理。let option_value: Option = None; if let Some(value) = option_value { println!("The value is: {}", value); } else { println!("There is no value!"); }上述代码会输出:There is no value!
- Josh Lee
@JoshLee 那个仓库看起来非常值得全文阅读!谢谢。 - Kyle Strand
1个回答

19

什么情况下需要迭代 Option

我的最爱,一句话概括,就是 flatten

fn main() {
    let results = [Some(1), None, Some(3), None];
    let sum: i32 = results.into_iter().flatten().sum();
    println!("{}", sum)
}

在 Rust 1.29 之前,可以使用 flat_map
fn main() {
    let results = vec![Some(1), None, Some(3), None];
    let sum: i32 = results.into_iter().flat_map(|x| x).sum();
    println!("{}", sum)
}

Option 可以被看作是一个容器,它可以容纳零个或一个元素。与 Vec 相比,它可以容纳零个或多个元素。在许多方面,Option 就像一个容器一样!

实现 IntoIterator 允许 Option 参与更多的 API。

请注意,Result 也实现了 IntoIterator,原因类似。

这非常令人困惑。

是的,确实如此,这就是为什么 Clippy 有一个 lint 来解决这个问题 的原因:

warning: for loop over `str_vec.get(0..3)`, which is an `Option`. This is more readably written as an `if let` statement
  --> src/main.rs:10:14
   |
10 |     for s in str_vec.get(0..3) {
   |              ^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(clippy::for_loops_over_fallibles)]` on by default
   = help: consider replacing `for s in str_vec.get(0..3)` with `if let Some(s) = str_vec.get(0..3)`
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles

这表明对于程序员来说,Option 并不像容器那样。

4
为什么要使用flat_map()而不是filter_map()?我觉得后者更适合这种情况... - Lukas Kalbertodt
1
@KyleStrand 堆分配方面实际上是正交的。Vec 使用堆来具有动态容量。还有基于栈的集合,通常具有固定容量。我只半心半意地同意 unwrap_or 的比较。在 这个 示例中,是的,它们有相同的结果(由于整数加法的工作方式),但使用 collect 而不是 sum 将显示它们的不同之处。 - Shepmaster
2
@KyleStrand:友情提醒,在Rust中,Box始终具有对象,没有空的Box :) - Matthieu M.
1
@KyleStrand 抱歉,我并不是想表达一个比另一个更加“functional”,只是在函数式语言(如Clojure)中,“flat-map”这个组合操作的名称已经存在,而“filter-map”则不然,根据我的经验通常需要分别使用filtermap操作来完成。Rust之所以将两者结合起来,是出于所有权方面的考虑。 - Shepmaster
1
另外,如果您选择使用 filter_map,则不会自动删除 Result;您必须显式转换为 Option - Shepmaster
显示剩余5条评论

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