如何编写一个带迭代器的Rust函数?


72

我想编写一个接受迭代器并返回一些操作结果的函数。具体来说,我正在尝试迭代a的值HashMap

use std::collections::HashMap;

fn find_min<'a>(vals: Iterator<Item=&'a u32>) -> Option<&'a u32> {
    vals.min()
}

fn main() {
    let mut map = HashMap::new();
    map.insert("zero", 0u32);
    map.insert("one", 1u32);
    println!("Min value {:?}", find_min(map.values()));
}

可惜:

error: the `min` method cannot be invoked on a trait object
 --> src/main.rs:4:10
  |
4 |     vals.min()
  |          ^^^

error[E0277]: the trait bound `std::iter::Iterator<Item=&'a u32> + 'static: std::marker::Sized` is not satisfied
 --> src/main.rs:3:17
  |
3 | fn find_min<'a>(vals: Iterator<Item = &'a u32>) -> Option<&'a u32> {
  |                 ^^^^ `std::iter::Iterator<Item=&'a u32> + 'static` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `std::iter::Iterator<Item=&'a u32> + 'static`
  = note: all local variables must have a statically known size

error[E0308]: mismatched types
  --> src/main.rs:11:41
   |
11 |     println!("Min value {:?}", find_min(map.values()));
   |                                         ^^^^^^^^^^^^ expected trait std::iter::Iterator, found struct `std::collections::hash_map::Values`
   |
   = note: expected type `std::iter::Iterator<Item=&u32> + 'static`
              found type `std::collections::hash_map::Values<'_, &str, u32>`

如果我尝试通过引用,则会遇到相同的错误;如果使用Box,则会出现终身错误。

Answers:


69

您要在此处使用泛型:

fn find_min<'a, I>(vals: I) -> Option<&'a u32>
where
    I: Iterator<Item = &'a u32>,
{
    vals.min()
}

特性可以以两种方式使用:作为类型参数的界限和特征对象。《Rust编程语言》一书中有关于特征的一章和关于特征对象的一章,它们解释了这两个用例。

此外,您通常希望采用一些实现的方法,IntoIterator因为这可以使调用函数的代码更好:

fn find_min<'a, I>(vals: I) -> Option<&'a u32>
where
    I: IntoIterator<Item = &'a u32>,
{
    vals.into_iter().min()
}

我好近 要说明的是,泛型的区别在于静态调度,即Rust是否为我使用它的每种具体类型创建此函数的版本?
J博士

2
正确。这样,编译器就知道迭代器的类型,因此就知道其大小,因此就知道需要保留多少内存。同样,Iterator<Item=&'a u32>不能单独使用like之类的类型;它只能在指针后面使用。
弗朗西斯·加涅

17
请注意,如果您想将任意迭代器传递到函数中,则虽然要求I实现Iterator绝对正确,但更为通用的方式是要求I实现IntoIterator。它也允许您传递迭代器,但是您也可以传递任何可以转换为迭代器的内容,而无需显式调用转换方法。我会说这是使用迭代器和可迭代对象惯用方法。
弗拉基米尔·马特维耶夫(Fladimir Matveev)

@VladimirMatveev,请您详细说明一下吗?我正在尝试通过您的建议获取类似以下内容的信息…… gist.github.com
anonymous/

@burfl:您的函数应该返回u64,而不是T
弗朗西斯·加涅

13

与具有Python背景而不是C ++背景的用户相比,这种行为有点不直观。因此,让我澄清一下。

在Rust中,值在概念上存储在绑定它们的名称中。因此,如果你写

let mut x = Foo { t: 10 };
let mut y = x;
x.t = 999;

y.t仍然会10

所以当你写

let x: Iterator<Item=&'a u32>;

(或函数参数列表中的相同),Rust需要为type的任何值分配足够的空间Iterator<Item=&'a u32>。即使这是可能的,它也不是有效的。

因此,Rust要做的是为您提供

  • 将值放在堆上,例如。使用Box,可提供Python样式的语义。然后,您通常可以使用&mut Iterator<Item=&'a u32>

  • 为每种可能的类型专门化每个函数调用,以满足限制。这是更灵活的,因为特征引用是可能的专业化,并且为编译器提供了更多的专业化机会,但是这意味着您不能进行动态分派(类型可以根据运行时参数而变化)。


这里的示例无法编译,let mut y = x分配移动x,因此第3行不能再使用x。(除非您指定Foo为Copy,但是已经给出了一个很好的暗示,即它的行为不会像在脚本语言中所期望的那样)
K. Norbert

无论“更改代码还是更改允许的内容”的语义,上面的示例代码仍然无效,并且无法编译。
诺伯特

我认为它是非复制的,因为这是结构的默认值,并且在您的答案中没有提到它是复制。
诺贝尔

问题在于,您用来推理的代码是无效的,没有一些初学者可能没有的任意假设。
诺贝尔

7

最简单的方法是使用impl Traitimpl Iterator

use std::collections::HashMap;

fn find_min<'a>(vals: impl Iterator<Item = &'a u32>) -> Option<&'a u32> {
    vals.min()
}

fn main() {
    let mut map = HashMap::new();
    map.insert("zero", 0u32);
    map.insert("one", 1u32);
    println!("Min value {:?}", find_min(map.values()));
}

操场

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.