如何在Rust中将迭代器正确传递给函数 [英] How to properly pass Iterators to a function in Rust

查看:118
本文介绍了如何在Rust中将迭代器正确传递给函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want to pass Iterators to a function, which then computes some value from these iterators. I am not sure how a robust signature to such a function would look like. Lets say I want to iterate f64. You can find the code in the playground: 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()
}

如果我们尝试遍历切片,则编译失败

This fails to compile if we try to iterate over slices

所以你可以做

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

如果我尝试遍历映射的范围,则无法编译. (为什么编译器在这里需要实时参数?)

This fails to compile if I try to iterate over mapped Ranges. (Why does the compiler requires the livetime parameters here?)

因此,我尝试接受引用,而不是一般性引用:

So I tried to accept references and not references generically:

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

这适用于我尝试过的所有组合,但是它很冗长,我并不太了解它的每个方面.

This works with all combinations I tried, but it is quite verbose and I don't really understand every aspect of it.

还有更多的情况吗?

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

What would be the best practice of solving this problem?

推荐答案

没有 right 方法来编写可以接受Iterator的函数,但是我们可以遵循一些通用原则适用于使您的功能通用且易于使用.

There is no right way to write a function that can accept Iterators, but there are some general principles that we can apply to make your function general and easy to use.

  1. 编写接受impl IntoIterator<...>的函数.因为所有Iterator实现都实现IntoIterator,所以它比仅接受impl Iterator<...>的函数严格得多.
  2. Borrow<T>是对T&T进行抽象的正确方法.
  3. 当特征界限变得冗长时,如果将其写在where子句中而不是内联,则通常更容易阅读.
  1. Write functions that accept impl IntoIterator<...>. Because all Iterators implement IntoIterator, this is strictly more general than a function that accepts only impl Iterator<...>.
  2. Borrow<T> is the right way to abstract over T and &T.
  3. When trait bounds get verbose, it's often easier to read if you write them in where clauses instead of in-line.

记住这些,这就是我可能写dot的方法:

With those in mind, here's how I would probably write 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的答案,因为在每种情况下不一定都需要这种普遍性.这个dot很好,因为它可以接受广泛的参数,因此您可以调用dot(&some_vec, some_iterator)并且它可以正常工作.它针对呼叫站点的可读性进行了优化.另一方面,如果您发现Borrow特性使定义变得过于复杂,则在定义上优化可读性并有时会强迫调用者添加.iter().copied()并没有什么错.对于第一个dot函数,我肯定会更改的唯一事情是将Iterator替换为IntoIterator.

However, I also agree with TobiP64's answer in that this level of generality may not be necessary in every case. This dot is nice because it can accept a wide range of arguments, so you can call dot(&some_vec, some_iterator) and it just works. It's optimized for readability at the call site. On the other hand, if you find the Borrow trait complicates the definition too much, there's nothing wrong with optimizing for readability at the definition, and forcing the caller to add a .iter().copied() sometimes. The only thing I would definitely change about the first dot function is to replace Iterator with IntoIterator.

这篇关于如何在Rust中将迭代器正确传递给函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆