Skip to content
Lesson 12 of 15

Step 1 of 5 · Reading · ~4 min

Learn

Collections

Iterators

An iterator hands you the items of a sequence one at a time. By the end of this lesson you can chain iterator steps together to keep some items, transform them, and collapse them into a single answer, without writing a for loop or a mutable accumulator.

Making an iterator and consuming it

.iter() on a Vec gives an iterator that borrows each element in turn, just like for x in &v did in the Vec lesson. On its own it does nothing. A consumer is a method that pulls every item through and produces a result:

let scores = vec![4, 9, 2, 7];
let n = scores.iter().count();            // 4
let total: i64 = scores.iter().sum();     // 22

sum() can build several number types, so you name the one you want: let total: i64 = ... (or .sum::<i64>()). An empty sum is 0, so an iterator with no items gives 0, not an error.

Closures

A closure is a small unnamed function written inline. Parameters go between pipes, the body follows:

let add_tax = |price: i64| price + price / 10;
println!("{}", add_tax(200));   // 220

Iterator steps take closures to say what to do with each item.

Adapters: map and filter

An adapter takes an iterator and returns a new one. It does no work until a consumer runs the chain; iterators are lazy. If you write an adapter chain with no consumer, the compiler warns that iterators "do nothing unless consumed".

  • .map(f) replaces each item with f(item).
  • .filter(f) keeps an item only when f(item) is true.

Here is a different problem: temperatures above 20 count as heat, and you want the total degrees of heat.

let temps = vec![18, 23, 20, 27, 15];
let heat: i64 = temps.iter()
    .filter(|t| **t > 20)
    .map(|t| t - 20)
    .sum();
println!("{}", heat);   // 10

Trace it item by item:

itemfilter > 20map - 20running sum
18dropped0
23kept33
20dropped (not greater)3
27kept710
15dropped10

Why **t? .iter() yields &i64, and filter passes its closure a reference to each item, so t is &&i64. Two stars reach the number. In map, t is a plain &i64, and arithmetic on a reference works directly, producing an i64.

Order matters: filter sees the value before map changes it. Swap the two and you would be testing t - 20 > 20 instead.

The remainder trap with negatives

% gives the remainder, and in Rust its sign follows the left side:

expressionresult
7 % 21
-7 % 2-1
-6 % 20
9 % 30
-9 % 30

A remainder of 0 means "divides evenly" for negative numbers too. But a test like x % 2 == 1 is wrong for negatives: -7 % 2 is -1, so -7 is not recognised as odd. When you mean "not divisible", compare against zero with != 0.

Common mistakes

  • Testing oddness with == 1 and negating it. Negative odd numbers give a remainder of -1, so they slip through as even. For -4 -3 2 that prints 29 instead of 20.
  • Only doing half the pipeline. Adding the evens without squaring (8 1 gives 8, not 64), or squaring everything without filtering (1 2 3 4 5 gives 55, not 20).
  • Filtering by position instead of value. Keeping the items at even positions (0, 2, 4, ...) is a different question: 1 2 3 4 5 then gives 35.
  • Using count() where you need sum(). 2 4 6 prints 3, the number of items kept, instead of their total.

Your exercise

Print the sum of the squares of the even numbers on one line of input. The starter already parses the line into nums: Vec<i64> and prints total; you replace the 0 with an iterator chain. Watch the negative numbers: an even test that only works for positives will fail.

Up nextStructsStructs, Enums, and Error Handling

Discussion

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

Sign in to post a comment or reply.

Loading…