如何在Rust中将迭代器正确传递给函数 [英] How to properly pass Iterators to a function in 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 Iterator
s, but there are some general principles that we can apply to make your function general and easy to use.
- 编写接受
impl IntoIterator<...>
的函数.因为所有Iterator
实现都实现IntoIterator
,所以它比仅接受impl Iterator<...>
的函数严格得多. -
Borrow<T>
是对T
和&T
进行抽象的正确方法. - 当特征界限变得冗长时,如果将其写在
where
子句中而不是内联,则通常更容易阅读.
- Write functions that accept
impl IntoIterator<...>
. Because allIterator
s implementIntoIterator
, this is strictly more general than a function that accepts onlyimpl Iterator<...>
. Borrow<T>
is the right way to abstract overT
and&T
.- 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屋!