如何正确地将迭代器传递给 Rust 中的函数

Unl*_*kus 3 iterator rust

我想将迭代器传递给一个函数,然后该函数从这些迭代器中计算一些值。我不确定这样一个函数的健壮签名会是什么样子。假设我想迭代 f64。您可以在操场上找到代码:https://play.rust-lang.org/?version =stable&mode=debug&edition=2018&gist=c614429c541f337adb102c14518cf39e

我的第一次尝试是

fn dot(a : impl std::iter::Iterator<Item = f64>,b : impl std::iter::Iterator<Item = f64>) -> f64 {
    a.zip(b).map(|(x,y)| x*y).sum()
}
Run Code Online (Sandbox Code Playgroud)

如果我们尝试迭代切片,则编译失败

所以你可以做

fn dot<'a>(a : impl std::iter::Iterator<Item = &'a f64>,b : impl std::iter::Iterator<Item = &'a f64>) -> f64 {
    a.zip(b).map(|(x,y)| x*y).sum()
}
Run Code Online (Sandbox Code Playgroud)

如果我尝试迭代映射的范围,则无法编译。(为什么编译器在这里需要livetime参数?)

所以我尝试接受参考文献,而不是一般的参考文献:

pub fn dot<T : Borrow<f64>, U : Borrow<f64>>(a : impl std::iter::Iterator::<Item = T>, b: impl std::iter::Iterator::<Item = U>) -> f64 {
    a.zip(b).map(|(x,y)| x.borrow()*y.borrow()).sum()
}
Run Code Online (Sandbox Code Playgroud)

这适用于我尝试过的所有组合,但它非常冗长,我并不真正理解它的每个方面。

还有更多案例吗?

解决这个问题的最佳实践是什么?

tre*_*tcl 8

没有正确的方法来编写可以接受 s 的函数Iterator,但是我们可以应用一些通用原则来使您的函数通用且易于使用。

  1. 编写接受impl IntoIterator<...>. 因为所有Iterators 都实现了IntoIterator,所以这比仅接受的函数更通用impl Iterator<...>
  2. Borrow<T>T是抽象和的正确方法&T
  3. where当特征界限变得冗长时,如果将它们写在子句中而不是行内,通常会更容易阅读。

考虑到这些,我可能会这样写dot

fn dot<I, J>(a: I, b: J) -> f64
where
    I: IntoIterator,
    J: IntoIterator,
    I::Item: Borrow<f64>,
    J::Item: Borrow<f64>,
{
    a.into_iter()
        .zip(b)
        .map(|(x, y)| x.borrow() * y.borrow())
        .sum()
}
Run Code Online (Sandbox Code Playgroud)

不过,我也同意TobiP64 的回答,因为这种通用性可能并不是在每种情况下都需要。这dot很好,因为它可以接受广泛的参数,所以你可以调用它dot(&some_vec, some_iterator),它就可以工作。它针对调用站点的可读性进行了优化。另一方面,如果您发现该Borrow特征使定义过于复杂,那么优化定义的可读性强制调用者有时添加 a 并没有什么问题.iter().copied()。关于第一个函数,我唯一肯定会改变的dot就是替换IteratorIntoIterator.