Question

Accepting Iterators in Rust Functions with impl Iterator

rustfunctioniterator

Question

How can I write a Rust function that accepts an iterator, performs an operation on its items, and returns a result? For example, I want to pass the values iterator from a HashMap and return its smallest value:

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

This produces errors about trait objects, Sized, and mismatched iterator types. Passing the iterator by reference produces similar errors, while using Box leads to lifetime errors. What is the correct way to define this function?

Short Answer

Rust iterator methods work with many concrete iterator types, including HashMap::values(). You will learn why Iterator is a trait rather than a concrete parameter type, and how to accept iterators with a generic type parameter or impl Iterator.

Concept

Iterator is a trait: it describes behavior rather than naming one specific concrete type. Many types implement it, such as ranges, slices, vectors, and HashMap value views.

map.values() does not produce a value whose concrete type is literally Iterator<Item = &u32>. It produces a concrete library type similar to hash_map::Values<'_, &str, u32>, and that type implements the Iterator trait.

A function parameter must normally have a known size at compile time. Writing this is therefore not valid:

// Invalid: Iterator is a trait, not a concrete sized type.
fn find_min<'a>(vals: Iterator<Item = &'a u32>) -> Option<&'a u32> {
    vals.min()
}

Use one of these forms instead:

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

or the equivalent explicit generic form:

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

Both say: “accept any concrete type I that implements Iterator and yields &'a u32 values.”

This matters because iterator adapters such as .map(), .filter(), and .enumerate() create their own concrete iterator types. Generic iterator parameters let one function work with all of them without allocating or boxing.

Mental Model

Think of Iterator as a job description, not a physical object.

  • Iterator says: “I can provide items one at a time.”
  • HashMap::Values is one particular worker that has that job.
  • A range such as 1..10 is another worker with the same job.

A parameter written as I: Iterator means “send me any worker qualified for this job.” Rust still knows the worker's exact concrete type when compiling, so it knows its size and can optimize the code.

A trait object such as dyn Iterator is different: it is like hiding the worker's exact identity behind a common interface. That can be useful, but it requires indirection such as &mut dyn Iterator<_> or Box<dyn Iterator<_>>. For ordinary iterator-consuming functions, generics are usually simpler.

Take Quiz

Syntax and Examples

Use impl Iterator when the function consumes an iterator and only needs iterator behavior:

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

A complete example with HashMap::values():

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);
    map.insert("two", 2u32);

    println!("Min value: {:?}", find_min(map.values()));
}

Output:

Min value: Some(0)

map.values() yields &u32 because the map owns its u32 values. The function returns Option<&u32> for the same reason:

  • Some(&0) when at least one value exists.
  • None when the map is empty.

The explicit generic version is equivalent and is useful when you want to name the iterator type:

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

Step by Step Execution

Consider this example:

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("apple", 12u32);
    map.insert("pear", 4u32);
    map.insert("orange", 9u32);

    let smallest = find_min(map.values());
    println!("{smallest:?}");
}

Execution:

  1. map stores three key-value pairs.
  2. map.values() creates a Values iterator. It borrows the map and yields references like &12, &4, and &9.
  3. Rust chooses the concrete Values type for the impl Iterator parameter at this call site.
  4. .min() consumes the iterator, comparing each yielded &u32 by the numeric value of the referenced u32.
  5. The smallest reference, &4, is returned as Some(&4).
  6. The reference is valid only while map remains alive and unchanged in ways that conflict with the borrow.

The iteration order of a HashMap is not sorted and should not be relied on. .min() examines all values, so it still finds the correct minimum regardless of iteration order.

Real World Use Cases

Iterator-accepting functions are useful whenever your logic should work with multiple data sources.

  • Report calculations: Find the smallest invoice amount from a vector, database result iterator, or map values.
  • Validation: Check whether any submitted field is empty with .any(...).
  • Data cleanup: Filter invalid records and transform valid ones with .filter(...) and .map(...).
  • API processing: Consume pages or streamed records one item at a time.
  • Log analysis: Find the latest timestamp or highest severity level without first creating another collection.

For example, this function works with any iterator of numbers:

fn count_large(values: impl Iterator<Item = u32>) -> usize {
    values.filter(|value| *value > 100).count()
}

let from_vec = count_large(vec![50, 120, 250].into_iter());
let from_range = count_large(90..105);

assert_eq!(from_vec, 2);
assert_eq!(from_range, 4);
Take Quiz

Real Codebase Usage

In real Rust code, choose the parameter based on what your function needs.

Consume an existing iterator

Use impl Iterator or a generic I: Iterator when callers already have an iterator:

fn has_negative(values: impl Iterator<Item = i32>) -> bool {
    values.any(|value| value < 0)
}

This consumes values because methods such as .any() take ownership of the iterator.

Accept collections and iterators

Use IntoIterator when callers may pass a collection directly or an iterator:

fn sum_nonzero<I>(values: I) -> i32
where
    I: IntoIterator<Item = i32>,
{
    values.into_iter().filter(|value| *value != 0).sum()
}

assert_eq!(sum_nonzero(vec![1, 0, 3]), 4);
assert_eq!(sum_nonzero(1..4), 6);

Use iterator chains instead of temporary collections

A common pattern is to pass an adapter chain directly:

fn first_long_name<'a>(names: impl Iterator<Item = &'a str>) -> Option<&'a str> {
    names.find(|name| name.len() >= 8)
}

let names = ["Ada", "Grace", "Margaret"];
let result = first_long_name(names.iter().copied());
assert_eq!(result, Some("Margaret"));

This avoids allocating an intermediate Vec solely to perform another operation.

Common Mistakes

Using a trait as though it were a concrete type

// Does not compile.
fn find_min<'a>(values: Iterator<Item = &'a u32>) -> Option<&'a u32> {
    values.min()
}

Iterator has no known size by itself. Use impl Iterator or a generic type bound.

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

Expecting an iterator to be reusable after consumption

let values = vec![3, 1, 2].into_iter();
let smallest = values.min();
// values.count(); // Error: values was moved into min().

Most terminal iterator methods, including .min(), consume the iterator. Create another iterator if you need another pass.

Returning a reference after the owner is gone

// Does not compile: the map is dropped before the returned reference could be used.
fn invalid() -> Option<&'static u32> {
    let map = std::collections::HashMap::from([("one", 1u32)]);
    find_min(map.values())
}

A returned &u32 points into the map, so the map must outlive the reference. Return an owned u32 instead when the result must outlive the collection:

fn find_min_owned(values: impl Iterator<Item = u32>) -> Option<u32> {
    values.min()
}

Adding Box unnecessarily

// Usually more complex than necessary.
fn find_min<'a>(values: Box<dyn Iterator<Item = &'a u32> + 'a>) -> Option<&'a u32> {
    values.min()
}

This can work in some designs, but it allocates on the heap and adds lifetime details. Prefer generics unless you specifically need to store or return different iterator types through one runtime interface.

Assuming HashMap iteration is ordered

HashMap does not promise insertion order or sorted key order. .min() is safe because it checks every item, but do not treat the first value yielded by map.values() as the smallest or earliest value.

Comparisons

ApproachBest whenExample parameterNotes
impl IteratorThe function accepts and consumes one iteratorvalues: impl Iterator<Item = u32>Concise syntax for a generic parameter.
Generic I: IteratorYou need to name or reuse the iterator type in boundsfn f<I: Iterator>(values: I)Equivalent in capability to one impl Iterator parameter.
IntoIteratorCallers might pass a collection or an iteratorvalues: impl IntoIterator<Item = u32>Call .into_iter() inside the function.
&mut impl IteratorThe caller should retain the iterator and its current positionvalues: &mut impl Iterator<Item = u32>The function advances the caller's iterator.
&mut dyn IteratorRuntime polymorphism is genuinely neededvalues: &mut dyn Iterator<Item = u32>No heap allocation, but dynamic dispatch.
Box<dyn Iterator>You must own a dynamically selected iteratorBox<dyn Iterator<Item = u32>>Heap allocation and dynamic dispatch.

For the original find_min function, impl Iterator is the clearest choice. If you want callers to pass a Vec<u32> directly, use IntoIterator<Item = u32> and return an owned number.

Take Quiz

Cheat Sheet

// Accept any iterator that yields owned u32 values.
fn total(values: impl Iterator<Item = u32>) -> u32 {
    values.sum()
}

// Equivalent generic form.
fn total_generic<I>(values: I) -> u32
where
    I: Iterator<Item = u32>,
{
    values.sum()
}

// Accept a collection or iterator.
fn total_any(values: impl IntoIterator<Item = u32>) -> u32 {
    values.into_iter().sum()
}

// Accept references and return a reference.
fn find_min<'a>(values: impl Iterator<Item = &'a u32>) -> Option<&'a u32> {
    values.min()
}
  • Iterator is a trait, not a concrete parameter type.
  • impl Iterator<Item = T> accepts one compiler-known concrete iterator type per call.
  • Terminal methods such as .min(), .sum(), .collect(), .count(), and .any() consume the iterator.
  • Item = &T means the iterator yields borrowed values; returned references cannot outlive their owner.
  • Use IntoIterator when accepting collections directly is more ergonomic.
  • HashMap::values() yields references to the map's values.

FAQ

Why does Iterator<Item = T> fail as a function parameter in Rust?

Iterator is a trait, and traits do not have a known concrete size by default. Use impl Iterator<Item = T>, a generic type parameter such as I: Iterator<Item = T>, or a trait object behind &mut or Box.

Should I use impl Iterator or I: Iterator?

Use impl Iterator for simple function parameters. Use I: Iterator when you need to refer to the iterator type by name, such as in multiple bounds or associated type relationships.

Why does find_min(map.values()) return Option<&u32>?

map.values() borrows values stored inside the map, so its items are &u32. The minimum is therefore also a reference, wrapped in Option for the empty-map case.

Can I return Option<u32> instead?

Yes, if you iterate over owned u32 values or copy the references:

let min: Option<u32> = map.values().copied().min();

Does calling .min() sort a HashMap?

No. .min() scans the iterator and remembers the smallest item. It does not modify or sort the map.

When should I use Box<dyn Iterator>?

Use it when a value must hold one of several different iterator types selected at runtime, such as returning different iterator pipelines from different branches. It is usually unnecessary for a function that only processes an iterator immediately.

Why does passing an iterator by reference change the signature?

A reference changes the parameter type and borrowing behavior. Use &mut impl Iterator<Item = T> only when the function should advance an iterator that the caller continues to own. For a one-time operation like .min(), passing the iterator by value is usually right.

Take Quiz

Mini Project

Description

Build a small utility for analyzing product prices stored in a HashMap. The utility should accept any iterator of borrowed prices, find the lowest price, and keep the returned reference tied safely to the original map.

Goal

Print the name-independent lowest product price from a HashMap by passing map.values() to a reusable iterator function.

Requirements

Use a HashMap containing at least three product names and u32 prices. Write a lowest_price function that accepts any iterator yielding &u32. Return Option<&u32> so an empty input is handled safely. Call the function with prices.values(). Print the result with Debug formatting.

Take Quiz

Keep learning

Related questions

View more