std :: iter :: Iterator :: Collect函数的工作方式

时间:2019-12-28 23:15:44

标签: rust iterator documentation collect

我正在尝试通过阅读一些文档here了解集合功能的全部功能。但是,我遇到了一些挑战,特别是在页面上引用的最后一个示例中(也在下面列出了我的评论)

let results = [Ok(1), Err("nope"), Ok(3), Err("bad")];

let result: Result<Vec<_>, &str> = results.iter().cloned().collect();

// gives us the first error <-- Point 1
assert_eq!(Err("nope"), result);

let results = [Ok(1), Ok(3)];

let result: Result<Vec<_>, &str> = results.iter().cloned().collect();

// gives us the list of answers
assert_eq!(Ok(vec![1, 3]), result);

我用一些自己的代码(如下所示)跟踪了此代码

let results: [std::result::Result<i32, &str>; 2] = [Err("nope"), Err("bad")];

let result: Vec<Result<i32, &str>> = results.iter().cloned().collect();

// The following prints <-- Point 2
// "nope"
// "bad"
for x in result{
    println!("{:?}", x.unwrap_err());
}

看看在Result结构here上FromIterator特性的实现,我们看到它提到“采用Iterator中的每个元素:如果它是Err,则不进一步的元素被使用,并返回Err。如果没有Err出现,则返回包含每个Result的值的容器。

此说明与在点1看到的结果一致,但似乎不适用于点2。在点2,我期望仅打印“ nope”而不是两个值。

因此,我试图了解这种(选择性)转换发生在哪里并面临挑战。

如果我们看一下方法定义本身,就会看到以下内容。

#[inline]
fn from_iter<I: IntoIterator<Item=Result<A, E>>>(iter: I) -> Result<V, E> {
    // FIXME(#11084): This could be replaced with Iterator::scan when this
    // performance bug is closed.

    iter::process_results(iter.into_iter(), |i| i.collect())
}

它表明在迭代器上正在调用into_iter()方法。搜索“ into_iter”给出了两种实现

#[stable(feature = "rust1", since = "1.0.0")]
impl<T, E> IntoIterator for Result<T, E> {
    type Item = T;
    type IntoIter = IntoIter<T>;

    /// Returns a consuming iterator over the possibly contained value.
    ///
    /// The iterator yields one value if the result is [`Result::Ok`], otherwise none.
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```
    /// let x: Result<u32, &str> = Ok(5);
    /// let v: Vec<u32> = x.into_iter().collect();
    /// assert_eq!(v, [5]);
    ///
    /// let x: Result<u32, &str> = Err("nothing!");
    /// let v: Vec<u32> = x.into_iter().collect();
    /// assert_eq!(v, []);
    /// ```
    #[inline]
    fn into_iter(self) -> IntoIter<T> {
        IntoIter { inner: self.ok() }
    }
}

#[stable(since = "1.4.0", feature = "result_iter")]
impl<'a, T, E> IntoIterator for &'a Result<T, E> {
    type Item = &'a T;
    type IntoIter = Iter<'a, T>;

    fn into_iter(self) -> Iter<'a, T> {
        self.iter()
    }
}

但是,由于我对语言的有限了解,似乎没有人能够解释文档所说的内容以及Point 2中发生的情况。

有人可以解释一下它是如何工作的,或者可以指出我在实现这种选择逻辑的源代码中的正确位置吗?

编辑:只是为了澄清一点,我想理解的不是为什么我们在向量中得到所有值而在结果中只得到一个值,而是一个。在哪里是从值列表中选择第一个Err的代码/逻辑和b。为什么在列表中收集结果时会选择多个Err值(根据文档,它应该只是第一个Err值)

2 个答案:

答案 0 :(得分:3)

在此示例中

let result: Vec<Result<i32, &str>> = results.iter().cloned().collect();

您没有收集到Result中,而是收集到了Vec中,因此所有值都被收集且未被修改。可以从Vec获得。

这根本不同于

let result: Result<Vec<_>, &str> = results.iter().cloned().collect();

收集到Result中,该位置根据是否找到Err来过滤元素。来自impl<A, E, V> FromIterator<Result<A, E>> for Result<V, E> where V: FromIterator<A>,

答案 1 :(得分:0)

Iterator::collect 定义为:

fn collect<B: FromIterator<Self::Item>>(self) -> B
where
    Self: Sized,
{
    FromIterator::from_iter(self)
}

所有有趣的实现都是通过 FromIterator trait 完成的。每种类型都可以选择如何实现这个特征。

ResultVec 的实现都经过优化和抽象,因此指向一行代码并说“这就是它的位置”并非易事。

Result

这会调用 iter::process_results,这是一个内部辅助方法,用于“提升”Result 的迭代器(您可以在 Itertools::process_results 中看到类似的东西)。如果遇到任何 Result::Err,则迭代停止并显示该错误。否则,闭包将使用仅包含成功值的迭代器执行。闭包对内部集合类型调用 Iterator::collect,生成成功值的 Vec

Vec

这通过 SpecFromIter 辅助特征执行一些内部专业化间接。完成所有这些之后,您将看到创建了一个空的 Vec,然后extend使用迭代器中的所有项目进行了处理。