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

我想编写一个函数,它接受一个迭代器并返回它上面的一些操作的结果。具体来说,我尝试迭代 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,则会得到生存期错误。

38783 次浏览

You want to use generics here:

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

Traits can be used in two ways: as bounds on type parameters and as trait objects. The book The Rust Programming Language has a chapter on traits and a chapter on trait objects that explain these two use cases.

Additionally, you often want to take something that implements IntoIterator as this can make the code calling your function nicer:

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

This behaviour is a little unintuitive from those with a Python background rather than, say, a C++ background, so let me clarify a little.

In Rust, values are conceptually stored inside the name that binds them. Thus, if you write

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

y.t will still be 10.

So when you write

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

(or the same in the function parameter list), Rust needs to allocate enough space for any value of type Iterator<Item=&'a u32>. Even if this was possible, it wouldn't be efficient.

So what Rust does instead is offer you the option to

  • Put the value on the heap, eg. with Box, which gives Python-style semantics. Then you can take generically with &mut Iterator<Item=&'a u32>.

  • Specialize each function invocation for each possible type to satisfy the bound. This is more flexible, since a trait reference is a possible specialization, and gives the compiler more opportunities for specialization, but means you can't have dynamic dispatch (where the type can vary dependent on runtime parameters).

Since Rust 1.26 impl Trait are available. Here is a more compact version using impl Trait.

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

playground