19.5Consuming iterators

Last updated June 13, 2026

Adapters build a pipeline but never run it (lesson 19.4). A consumer is the method at the end that actually drives the iterator, calling next over and over until it returns None, and folds the stream of values into a single result. Consumers are where laziness ends and work happens. We've leaned on collect already; this lesson gives it and its siblings their proper treatment.

collect: gather into a collection

collect runs the iterator and gathers every item into a collection. The catch, and the reason the turbofish keeps appearing, is that collect can build many different collections, a Vec, a String, a HashMap, so you must tell it which one you want. There are two ways, and you've already used the syntax for both in chapter 5.

The first is to annotate the binding's type, and let collect follow it:

fn main() {
    let v = vec![1, 2, 3];
    let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
    println!("{doubled:?}");
}
[2, 4, 6]

The : Vec<i32> on the left tells collect its target. The second way is the turbofish (lesson 5.6), naming the type right on collect itself:

let doubled = v.iter().map(|x| x * 2).collect::<Vec<i32>>();

This is the exact same situation as parse::<i32>(): a generic function whose return type the compiler can't infer from the arguments, so you name it, either through an annotation or through the ::<> fish. They're interchangeable; pick whichever reads better. When the element type is obvious from the pipeline, Vec<_> lets the compiler fill in the blank: .collect::<Vec<_>>().

collect into a String joins an iterator of chars or &strs into one string, and collect into a HashMap (chapter 18) builds a map from an iterator of (key, value) tuples:

use std::collections::HashMap;

fn main() {
    let shout: String = "hello".chars().map(|c| c.to_ascii_uppercase()).collect();
    println!("{shout}");

    let scores: HashMap<&str, i32> = vec![("ada", 9), ("alan", 7)].into_iter().collect();
    println!("{:?}", scores.get("ada"));
}
HELLO
Some(9)

collect is the inverse of iteration

Iteration takes a collection apart into a stream of items; collect puts a stream of items back together into a collection. Which collection is the one question collect can't answer on its own, which is why it's the method that most often needs a turbofish or a type annotation. Any time you see collect with no obvious target type, that's the missing-type question of lesson 5.6, and the same two answers apply.

sum and count

For the common reductions there are named consumers. sum adds up the items; count reports how many there are. sum needs to know the result type (an annotation does it):

fn main() {
    let nums = vec![1, 2, 3, 4, 5];
    let total: i32 = nums.iter().sum();
    let how_many = nums.iter().filter(|x| *x % 2 == 0).count();
    println!("total {total}, evens {how_many}");
}
total 15, evens 2

count walks the whole iterator just to tally it, so use it when you genuinely need the count of a filtered stream; if you only want the length of a plain vector, vec.len() is instant and vec.iter().count() is wasteful.

fold: the general reduction

sum is a special case of a more general tool. fold carries an accumulator through the iteration: you give it a starting value and a closure that combines the accumulator with each item to produce the next accumulator. The final accumulator is the result:

fn main() {
    let nums = vec![1, 2, 3, 4];
    let product = nums.iter().fold(1, |acc, x| acc * x);
    println!("{product}");
}
24

Read it as: start the accumulator at 1; for each x, replace the accumulator with acc * x. So 1*1*2*3*4 = 24. sum is fold(0, |acc, x| acc + x) with a nicer name. fold is the escape hatch for reductions that don't have a built-in: building a string, finding a running maximum, counting with conditions. When you can't find a named consumer for the summary you want, fold can always express it.

find, any, all: short-circuiting searches

The last group answers questions about the stream, and they're special because they stop early. They don't have to walk the whole iterator if the answer is settled sooner.

find returns the first item matching a predicate, as an Option, and stops at the first match:

fn main() {
    let nums = vec![1, 3, 5, 8, 9];
    let first_even = nums.iter().find(|x| *x % 2 == 0);
    println!("{first_even:?}");
}
Some(8)

any returns true if any item matches (stopping at the first that does); all returns true if every item matches (stopping at the first that doesn't):

let nums = vec![2, 4, 6, 7];
println!("{}", nums.iter().any(|x| *x % 2 == 1));   // true  (7 is odd)
println!("{}", nums.iter().all(|x| *x % 2 == 0));   // false (7 isn't even)

The short-circuiting is the reason to prefer any over, say, filter(...).count() > 0: any can return the instant it finds one match, even on a million-element (or infinite) iterator, while the count version would dutifully walk everything. Choosing the consumer that asks the precise question lets the iterator do the least possible work.

Best practice

Reach for the most specific consumer that answers your question. "Is there a match?" is any, not filter().count() > 0. "What's the first match?" is find, not filter().next(). "How many?" is count. The specific consumers are clearer to read and let the iterator short-circuit, doing only as much work as the answer requires.

A note on owned versus borrowed results

One thing that surfaces with consumers: whether you used iter() or into_iter() (lesson 19.3) determines whether you collect references or owned values. v.iter().collect::<Vec<&i32>>() gathers references (and v survives); v.into_iter().collect::<Vec<i32>>() gathers owned values (and consumes v). For Copy types the adapter .copied() (or .cloned() for non-Copy) converts an iterator of &T into one of T mid-pipeline, which is why you saw .copied() in the zip example last lesson, it let the result hold plain i32s instead of &i32s. The ownership question never goes away; it just rides along through the pipeline.

Quiz time

Question #1

Why does collect so often need a turbofish or a type annotation, when sum and count usually don't?

Show solution

collect can build many different target collections (Vec, String, HashMap, and more) from the same iterator, so the compiler can't infer which one you want; you specify it with a binding annotation (let v: Vec<_> = ...) or the turbofish (.collect::<Vec<_>>()), exactly the missing-type situation from lesson 5.6. count always returns a usize and sum's type is usually pinned by an annotation on a single number, so there's less (or nothing) left to infer.

Question #2

Rewrite vec![1, 2, 3, 4].iter().sum::<i32>() using fold, and explain the two arguments to fold.

Show solution
vec![1, 2, 3, 4].iter().fold(0, |acc, x| acc + x)   // 10

fold's first argument is the starting accumulator (0). Its second is a closure taking the current accumulator and the next item, returning the new accumulator (acc + x). The final accumulator after the whole iterator is the result. sum is just this with a built-in name and a starting value of zero.

Question #3

You want to know whether a list contains any negative number. Why is list.iter().any(|x| *x < 0) better than list.iter().filter(|x| *x < 0).count() > 0?

Show solution

any short-circuits: it stops and returns true the instant it finds the first negative, so on a long (or infinite) list it can finish immediately. The filter().count() > 0 version walks the entire list counting matches before comparing, doing far more work for the same yes/no answer. any is also clearer about the question being asked.

We promised back in chapter 7 that the for loop was iterators in disguise. The next lesson (19.6) finally pays that off: exactly how for desugars into next calls through the IntoIterator trait.