Skip to content
Iterators
step 1/5

Reading — step 1 of 5

Learn

~3 min readCollections

Iterators

Every loop you've written in this course walks a sequence, filters or transforms it, and collapses it into an answer. Rust's iterators make those three steps vocabulary — and unlike most languages' versions, they compile down to the same machine code as your hand-written loop. Zero-cost abstraction is Rust's brand; iterators are exhibit A.

The pipeline shape

let nums = vec![1, 2, 3, 4, 5, 6];

let total: i64 = nums.iter()          // 1. make an iterator
    .filter(|n| *n % 2 == 0)          // 2. keep evens        (adapter)
    .map(|n| n * n)                   // 3. square them       (adapter)
    .sum();                           // 4. collapse          (consumer)
// 4 + 16 + 36 = 56

Those |n| n * n things are closures — anonymous inline functions; parameters between pipes, body after. Adapters (filter, map, take, skip, rev, enumerate, zip…) each return a new iterator and do no work yet — iterators are lazy. Only a consumer makes the pipeline run:

.sum()                        // add everything
.count()                      // how many survived
.max() / .min()               // Option<…> — empty input is a real case!
.collect::<Vec<_>>()          // gather into a collection
.any(|n| n > 100)             // bool: does one match?
.position(|n| n == 42)        // Option<usize>: where?

Forget the consumer and the compiler warns: "iterators are lazy and do nothing unless consumed" — a warning that has saved everyone at least once.

Reading the pipeline vs reading the loop

The equivalent manual version:

let mut total = 0;
for n in &nums {
    if n % 2 == 0 {
        total += n * n;
    }
}

Same result, same performance — genuinely, to the instruction. The difference is what the reader must do: the loop makes them simulate mutable state to learn intent; the pipeline states intent directly — filter evens, square, sum. For a three-line loop it's a taste call. As logic grows, pipelines stay linear while loops grow nested ifs and flag variables — which is why idiomatic Rust leans pipeline, and why clippy (Rust's linter) will actively suggest them.

One mechanical note while it's fresh: .iter() borrows (elements arrive as references — hence *n to compare values), .into_iter() consumes the collection, and ranges are already iterators — (1..=100).sum() needs no vector at all. When the closure's types fight you, that * or a .copied() adapter is usually the answer; the ownership chapter turns those guesses into understanding.

Your exercise: Sum of Squares of Evens

The pipeline above is the exercise — read the numbers, then:

let answer: i64 = nums.iter()
    .filter(|n| *n % 2 == 0)
    .map(|n| n * n)
    .sum();

Deliberate practice: write it both ways — manual loop first, pipeline second — and check they agree on the sample input. That A/B is the fastest route to reading real Rust, where every codebase you'll ever touch is written in the second style. Watch the edge case both versions must share: no evens in the input means the answer is 0, which sum() gives you for free (an empty sum is zero — one more reason it's the right consumer here).

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…