【问题标题】:How to properly pass Iterators to a function in Rust如何正确地将迭代器传递给 Rust 中的函数
【发布时间】:2019-12-23 21:15:43
【问题描述】:

我想将迭代器传递给一个函数,该函数然后从这些迭代器中计算一些值。 我不确定这样一个函数的健壮签名会是什么样子。 假设我想迭代 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()
}

如果我们尝试迭代切片,这将无法编译

所以你可以这样做

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()
}

如果我尝试遍历映射范围,则编译失败。 (为什么编译器在这里需要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()
}

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

还有更多案例吗?

解决这个问题的最佳做法是什么?

【问题讨论】:

    标签: rust iterator


    【解决方案1】:

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

    1. 编写接受impl IntoIterator&lt;...&gt; 的函数。因为所有Iterators 都实现了IntoIterator,所以这比只接受impl Iterator&lt;...&gt; 的函数更通用。
    2. Borrow&lt;T&gt; 是抽象 T&amp;T 的正确方法。
    3. 当 trait bound 变得冗长时,如果将它们写在 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()
    }
    

    不过,我也同意TobiP64's answer 的观点,因为这种概括性水平可能并非在所有情况下都是必要的。这个dot 很好,因为它可以接受广泛的参数,所以你可以调用dot(&amp;some_vec, some_iterator),它就可以工作。它针对调用站点的可读性进行了优化。另一方面,如果您发现 Borrow 特征使定义过于复杂,则在 定义 处优化可读性并强制调用者有时添加 .iter().copied() 并没有错。对于第一个 dot 函数,我唯一肯定会改变的就是将 Iterator 替换为 IntoIterator

    【讨论】:

      【解决方案2】:

      您可以像这样使用第一个 dot 实现对切片进行迭代:

      dot([0, 1, 2].iter().cloned(), [0, 1, 2].iter().cloned());
      

      (https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.cloned) 或

      dot([0, 1, 2].iter().copied(), [0, 1, 2].iter().copied());
      

      (https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.copied)

      为什么编译器在这里需要livetime参数?

      据我所知,rust 中的每个引用都有一个生命周期,但编译器可以推断出简单的情况。但是在这种情况下,编译器还不够聪明,所以你需要告诉它迭代器产生的引用的生存时间。

      还有更多案例吗?

      您始终可以使用迭代器方法(如上述解决方案)来获取 f64 上的迭代器,因此您不必处理生命周期或泛型。

      解决这个问题的最佳做法是什么?

      我会推荐第一个版本(因此将它留给调用者将迭代器转换为Iterator&lt;f64&gt;),仅仅是因为它是最易读的。

      【讨论】:

      • 克隆方法是否对切片进行实际复制?还是可以省略?
      • 它不会复制整个切片,而是在需要时复制每个元素。这相当于取消引用&amp;f64。你也可以写iter.map(|v| *v)
      • 更复杂的类型,复制起来并不便宜?
      • 在这种情况下,您需要迭代引用(如 dot 的第 2 版)
      猜你喜欢
      • 2015-04-15
      • 1970-01-01
      • 2021-07-08
      • 2016-05-23
      • 1970-01-01
      • 2019-11-05
      • 1970-01-01
      • 2012-07-23
      • 1970-01-01
      相关资源
      最近更新 更多