How to write a Rust function that takes an iterator?

FunctionIteratorRust

Function Problem Overview


I'd like to write a function that accepts an iterator and returns the results of some operations on it. Specifically, I'm trying to iterate over the values of 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()));
}

But alas:

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>`

I get the same error if I try to pass by reference; if I use a Box, I get lifetime errors.

Function Solutions


Solution 1 - Function

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

Solution 2 - Function

Since Rust 1.26 impl Trait are available. A less verbose version.

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

Solution 3 - Function

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).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDoctor JView Question on Stackoverflow
Solution 1 - FunctionFrancis GagnéView Answer on Stackoverflow
Solution 2 - FunctionBenView Answer on Stackoverflow
Solution 3 - FunctionVeedracView Answer on Stackoverflow