← Fundamentals
Guided lesson 7 questions · Rust only

Rust iterators and adapters

Follow what each step of an iterator chain receives and passes on, and which call makes the chain run.

An iterator chain runs only when a consumer, such as collect, sum, or for_each, asks it for values. iter() lends each element, iter_mut() lends it for changing, and into_iter() hands it over and uses up the collection. Each adapter passes new items to the next step, and each closure receives exactly what its adapter gives it.

Written for Rust.

Before you begin

Why this matters

Most Rust code that walks a collection is a chain of iterator methods. The bugs in such chains often compile: a map that never runs, a count that starts from the wrong item, an error that is quietly left out. If you can say what each step receives and when the chain runs, you can predict these results before you run the code.

What this helps you reason about

  • Say what iter, iter_mut, and into_iter give, and what is left of the collection afterwards.
  • Spot a chain that never runs, and predict what enumerate, skip, zip, and filter pass on.
  • Choose a consumer, know which ones stop early, and collect many Results into one.

In JavaScript or Python, a call such as map on a list does its work at once and gives you a new list. In Rust, map only describes the work. A chain of steps runs when one last call asks for its values, and each step receives exactly what the step before it passes on: a reference, a mutable reference, or the value itself. This lesson follows a backpacking trip through Rome, Split, and Vienna, one step of a chain at a time. It builds on Map, filter, and reduce and Iterators, generators, and laziness, and does not teach them again.

Already know this? The short version
  • v.iter() gives &T, v.iter_mut() gives &mut T, and v.into_iter() gives T and moves v, so a later use of v is E0382.
  • Adapters (map, filter, enumerate, zip, take, skip, chain, rev) run nothing until a consumer asks for values; a map with no consumer gives warning: unused `Map` that must be used, and its closure never runs. Use for_each or a for loop to change elements in place.
  • Each adapter sees only what the one before passes on: skip(1).enumerate() numbers from 0 at the second item. filter’s and find’s closures receive &Item, which is &&T after iter().
  • collect and sum need a result type (E0282 without one); any, all, find, and position stop at the first answer; a consumer moves the iterator. collect into Result<Vec<_>, _> gives every value or the first Err, and stops reading there.
  • The section “What goes wrong with iterators in real programs?” covers filter_map dropping errors, zip on lists of different lengths, side effects in closures that stop early, and E0277 when &String is collected into Vec<String>.

Go to the final check· Go to the practice tickets

Words in this lesson (7)
iterator
A value with a next method that gives Some(value) for each value in turn and None when there are no more. A for loop calls next until it gets None.
move
Assigning, passing, or returning a value whose type is not Copy, such as a String. Ownership goes to the new binding, and the compiler rejects any later use of the old one.
iterator adapter
A method such as map or filter that takes an iterator and returns a new iterator. It runs nothing until something asks the new iterator for values.
closure
A function without a name, written |parameters| body, together with the variables from the surrounding code that it uses. It borrows those variables, or takes them when written with move.
compiler warning
A message from the compiler about code that compiles but looks like a mistake, such as a mut that nothing needs. The program is still built and can run.
consumer
An iterator method that asks for values and returns a result that is not an iterator, such as collect, sum, count, find, or for_each. A chain of adapters runs only when a consumer asks it for values.
Result
A type whose value is either Ok(x) for success or Err(e) for failure. Functions that can fail, such as parse, return a Result.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / Three kinds

What do iter, iter_mut, and into_iter give?

A Vec has three methods that make an iterator over its elements. They differ in what each item is:

  • cities.iter() gives a shared reference to each element, a &String. cities keeps all its elements.
  • cities.iter_mut() gives a mutable reference to each element, a &mut String, so the code can change the elements in place. The Vec must be declared with let mut.
  • cities.into_iter() gives the elements themselves, each a String. It takes cities by value: the Vec moves into the iterator, and each element moves out to the code that receives it.

After into_iter(), cities cannot be used again. The compiler reports:

error[E0382]: borrow of moved value: `cities`

with “`cities` moved due to this method call” under into_iter() and the note “this function takes ownership of the receiver `self`, which moves `cities`”.

So use iter() to read the elements, iter_mut() to change them in place, and into_iter() when the collection is no longer needed and its elements should go somewhere else, such as into another Vec.

main.rs
let mut nights = vec![2, 3, 1];
for n in nights.iter_mut() {
    *n += 1; // changes the element in nights
}
let cities = vec![String::from("Rome"), String::from("Split")];
for city in cities.iter() {
    println!("{}", city); // city is a &String
}
let kept: Vec<String> = cities.into_iter().collect();
// cities is moved; kept owns both Strings

iter() lends each element (&T), iter_mut() lends it for changing (&mut T), and into_iter() hands over each element (T) and uses up the collection.

Coming from Python: Python has one way to loop over a list, and the list stays. In Rust, the method you choose decides whether the loop reads, changes, or takes the elements, and into_iter() leaves nothing behind.

Precisely: the for loop and arrays

for x in &v, for x in &mut v, and for x in v call the same three methods, as For loops and iteration protocols showed. into_iter() decides by what it is called on: on a Vec it gives the elements, on a &Vec or a slice it gives references, because the collection it receives is itself a reference. Since the 2021 edition, [2, 3].into_iter() on an array also gives the elements by value.

Experiment

Compare what iter and into_iter give

iter() borrows cities and gives a &String for each element. into_iter() takes cities by value: the Vec moves into the iterator, and each String moves out to city. Each String is dropped at the end of its pass, and the Vec’s memory is freed when the loop ends.

Why it matters Choose the method by what the code must do with the elements: read them with iter(), take them with into_iter(). After into_iter(), the collection is gone.

Predict what city holds in each loop: a reference to a String, or the String itself. Then add the line println!("{} cities", cities.len()); after the second loop, and read the compiler error.

fn main() {
    let cities = vec![String::from("Rome"), String::from("Split")];
    for city in cities.iter() {
        println!("visit {}", city);
    }
    for city in cities.into_iter() {
        println!("leave {}", city);
    }
}
What to look for

At the stop on line 4, city shows an address, an arrow, and "Rome" (len=4, cap=4, data=0x…): iter() gives a reference. The address is the same as the data= address at the end of cities, so city points at the first element inside the Vec. The panel also lists iter, the loop’s hidden iterator; its ptr moves 24 bytes, one String, on each pass. At the stop on line 7, city shows "Rome" (len=4, cap=4, data=0x…) with no arrow: the String itself, with the same data= address as the first element of cities. The stepper still lists cities after into_iter. After leave Rome, its first element can show unreadable characters: Rome was dropped at the end of the pass, and the stepper shows old bytes that cities no longer owns. The run prints visit Rome, visit Split, leave Rome, leave Split. After the edit, the compiler reports error[E0382]: borrow of moved value: `cities`, with “`cities` moved due to this method call” under into_iter() and the note “this function takes ownership of the receiver `self`, which moves `cities`”.

Expected output

visit Rome
visit Split
leave Rome
leave Split

02 / Nothing runs

Why does an adapter do nothing on its own?

Each adapter, such as map or filter, returns a new iterator and runs nothing, as Map, filter, and reduce showed. That has one trap that the compiler does not stop.

map looks like a way to change every element: nights.iter_mut().map(|n| *n += 1);. This statement builds a Map iterator that holds the closure, and drops it at the end of the statement. Nothing asks the Map for an item, so the closure never runs, and nights keeps its old values. The program compiles. rustc prints a compiler warning, a message about code that compiles but looks like a mistake:

warning: unused `Map` that must be used

with the note “iterators are lazy and do nothing unless consumed”. The stepper does not show Rust warnings; cargo build or cargo check in a terminal does.

To run a closure for each element, end the chain with a consumer: a method that asks for values and returns something that is not an iterator. for_each(|n| *n += 1) asks for every item and calls the closure with it. A for loop does the same, and is often easier to read when the body changes things.

main.rs
let mut nights = vec![2, 3, 1];
// warning: unused `Map` that must be used
nights.iter_mut().map(|n| *n += 1);
println!("{:?}", nights); // [2, 3, 1]

nights.iter_mut().for_each(|n| *n += 1);
println!("{:?}", nights); // [3, 4, 2]

An adapter with no consumer after it never runs its closure; rustc warns, and the program runs without the change.

Coming from JavaScript: Array map runs its callback at once, even when the new array is thrown away. Rust’s map runs nothing until a consumer asks for values.

Precisely: where the warning comes from

The standard library marks every adapter #[must_use], and the lint unused_must_use warns when such a value is thrown away. let _ = nights.iter_mut().map(…); silences the warning and still runs nothing. map is meant to compute new values for a later step; for changes in place, for_each and a for loop say what they do.

Experiment

Add a night with map and with for_each

map returns a Map iterator that holds the closure and the iter_mut iterator. Nothing asks the Map for an item, so the closure never runs, and the Map is dropped at the end of the statement. for_each is a consumer: it asks for every item and calls the closure with each one.

Why it matters Use map to compute new values that a consumer will use. To change elements in place, use for_each or a for loop.

Predict what is printed. Then change map to for_each on line 3, and predict again.

fn main() {
    let mut nights = vec![2, 3, 1];
    nights.iter_mut().map(|n| *n += 1);
    println!("{:?}", nights);
}
What to look for

The stepper never stops inside the closure. Line 3 runs once, and at the next stop, on line 4, nights still shows [2, 3, 1]. The run prints [2, 3, 1]. rustc warned about line 3, warning: unused `Map` that must be used, but the stepper does not show Rust warnings. After the edit, the stepper stops on line 3 three more times, in a frame named main::main::{closure#0}. Its n shows an address and → 2, then → 3, then → 1. The first address is the data= address of nights, and each next one is 4 bytes further, one i32. In main’s frame, nights changes one element after each call, and the run prints [3, 4, 2].

Expected output

[2, 3, 1]

03 / Adapters

What does each adapter pass on?

An adapter takes the items of the iterator before it and passes on new items. Each adapter wraps the one before, so a chain is read from left to right:

  • map(f) passes on f(item) for each item.
  • filter(test) passes on the items for which test returns true.
  • enumerate() passes on pairs (index, item), counting from 0.
  • zip(other) passes on pairs (item, item of other), and ends when either side ends.
  • take(n) passes on the first n items; skip(n) leaves out the first n.
  • chain(other) passes on all the items, then all the items of other.
  • rev() passes on the items from the last to the first.

An adapter sees only what reaches it. enumerate counts the items it receives, so after skip(1), the first item it receives gets the number 0. To number items by their place in the collection, put enumerate first.

Each closure receives what its adapter gives it. map’s closure receives the item itself. filter’s closure only tests the item, so it receives a reference to it, &Item. With iter(), the item is already a &i32, so filter’s closure gets a &&i32: a reference to a reference. Comparing it with a number fails:

error[E0308]: mismatched types

with “expected `&&_`, found integer”. Write **n > 1 to follow both references, or take them off in the closure’s parameter: in |&&n| n > 1, the pattern &&n matches the two references, and n is the i32. Rust closure capture and Fn traits covers what a closure captures; this is only about its parameter.

main.rs
let cities = ["Rome", "Split", "Vienna"];
for (day, city) in cities.iter().enumerate().skip(1) {
    println!("{}: {}", day, city); // 1: Split, 2: Vienna
}

let nights = vec![2, 5, 1];
// n is a &&i32: filter lends each &i32 item
let long: Vec<&i32> = nights.iter().filter(|n| **n > 1).collect();
// [2, 5]

Each adapter works on what the adapter before it passes on: enumerate counts what it receives, and filter’s closure receives a reference to each item.

Precisely: which closures receive a reference

filter, find, skip_while, and take_while lend each item to the closure, &Item, because they pass the item itself on or give it back. map, for_each, any, all, and position give the item itself, Item. A method call follows references by itself, so for a String item, filter(|c| c.len() > 4) needs no *. The pattern |&&n| works only when the item can be copied out, as an i32 can. nights.iter().copied() passes on i32 values instead of &i32, so filter’s closure then gets a &i32.

Experiment

Number the cities after skipping one

Each adapter wraps the one before it and sees only what that one passes on. In skip(1).enumerate(), skip leaves out Rome, so Split is the first item enumerate receives, and it gets 0. In enumerate().skip(1), enumerate numbers all three cities first, and skip leaves out the pair (0, Rome).

Why it matters enumerate counts the items it receives. To number items by their place in the collection, put enumerate before any adapter that leaves items out.

Predict the number printed in front of each city. Then change line 3 to use .enumerate().skip(1), and predict again.

fn main() {
    let cities = ["Rome", "Split", "Vienna"];
    for (day, city) in cities.iter().skip(1).enumerate() {
        println!("{}: {}", day, city);
    }
}
What to look for

At the stop on line 4, day is 0 and city shows an address → "Split". The panel lists iter, the loop’s hidden iterator, from the outside in: {iter: {iter: core::slice::iter::Iter<&str> {…}, n: 0}, count: 1}. The outer count: 1 belongs to enumerate: the number it gives next. The inner n: 0 belongs to skip: how many items it still has to leave out. The run prints 0: Split, then 1: Vienna. After the edit, the nesting is the other way round, {iter: {iter: …, count: 2}, n: 0}. day is 1 at the first stop, and the run prints 1: Split, then 2: Vienna.

Expected output

0: Split
1: Vienna

04 / Consumers

Which call runs the chain, and what does it give back?

A consumer asks the chain for values and returns something that is not an iterator. Each one decides how many values it asks for:

  • collect() asks for every value and builds a collection. It can build a Vec, a String, a HashMap, and more, so it needs to know which: let more: Vec<u32> = … .collect(); or … .collect::<Vec<u32>>(). The ::<…> after a method name gives the type for that call; Vec<_> lets the compiler work out the element type. With no type at all, the compiler reports error[E0282]: type annotations needed, with help: consider giving `more` an explicit type.
  • sum() and count() ask for every value. sum needs its type too, and gives 0 when there are no values. count gives the number of values.
  • fold(start, f), from Map, filter, and reduce, asks for every value.
  • any(test) and all(test) give a bool. any stops at the first item that passes; all stops at the first item that fails.
  • find(test) gives Some(the first item that passes), or None, and stops there. position(test) gives Some(its index) or None, counting from 0 in the iterator it is called on.

A consumer takes the iterator by value: the iterator moves into the call and is used up. Calling a second consumer on the same iterator, long.count() and then long.sum(), gives error[E0382]: use of moved value: `long`. Make a new iterator for each pass, such as nights.iter(); it costs almost nothing.

main.rs
let nights = vec![2, 5, 1, 4];
let total: u32 = nights.iter().sum(); // 12
let long = nights.iter().filter(|n| **n > 3).count(); // 2
let any_long = nights.iter().any(|n| *n > 3); // true
let first = nights.iter().find(|n| **n > 3); // prints Some(5)
let at = nights.iter().position(|n| *n > 3); // Some(1)
let more: Vec<u32> = nights.iter().map(|n| n + 1).collect();
// [3, 6, 2, 5]

A consumer runs the chain and uses up the iterator. collect and sum need to know their result type, and any, all, find, and position stop at the first answer.

Coming from Java: A Java stream can run one terminal operation, and a second one throws IllegalStateException while the program runs. A Rust iterator moves into its consumer, so a second consumer is a compile error.

Precisely: stopping early

any, all, find, and position return as soon as the answer is known. The items after it are never read, and the closures of the adapters before them never run for those items. take(n) stops the same way. count, sum, max, min, and last must read every item. find returns a reference into the collection, &u32 here; {:?} prints the number it points to.

Experiment

Watch find stop at the first long stay

find asks for one item at a time and stops at the first one that passes the test. The items after it are never read, so the closure never runs for them. find gives a reference to that item, which {:?} prints as Some(5).

Why it matters Consumers such as find, any, all, and position may stop early. Do not rely on their closure running for every item.

Predict how many check lines are printed. Then change > 3 to > 9 on line 5, and predict again.

fn main() {
    let nights = vec![2, 5, 1, 4];
    let long = nights.iter().find(|n| {
        println!("check {}", n);
        **n > 3
    });
    println!("{:?}", long);
}
What to look for

Each stop inside the closure is in a frame named main::main::{closure#0}. n shows two arrows, 0x… → 0x… → 2: a reference to a reference, the &&i32 that find’s closure receives. The second address is the element inside nights: first the data= address of nights, then 4 bytes further. The closure runs for 2 and for 5, and then find returns. At the stop on line 7, long shows Some(0x… → 5): Some holds a reference, and its address is the 5 inside nights. The run prints check 2, check 5, Some(5). After the edit, no item passes. The closure runs for all four, and the run prints four check lines, then None.

Expected output

check 2
check 5
Some(5)

05 / Results

How do you collect values that can fail?

parse, from Printing values and reading input, returns a Result: Ok(value) for success or Err(error) for failure. A chain that parses each item passes on Results. Collected into a Vec<Result<u32, _>>, they stay one Result per item, and the code must look at each one.

collect can also build one Result from many. Ask for Result<Vec<u32>, _>. While every item is Ok, collect keeps the values in a Vec. At the first Err, it stops asking for items and returns that Err. The items after it are never read, and the closures before collect never run for them. So the whole result is Ok(all the values) or the first error.

Option works the same way: collecting Options into Option<Vec<u32>> gives Some(all the values), or None at the first None. sum works this way too: sum::<Result<u32, _>>() gives Ok(the total) or the first Err.

main.rs
let typed = ["2", "x", "4"];
let nights: Result<Vec<u32>, _> = typed
    .iter()
    .map(|t| t.parse::<u32>())
    .collect();
println!("{:?}", nights);
// Err(ParseIntError { kind: InvalidDigit })

Collect Results into Result<Vec<_>, _> to get every value or the first error; the items after the first error are never read.

Coming from Python: int() raises an exception at the first bad item, and a list comprehension stops there. Rust’s collect into Result stops at the same place, and returns the error as a value.

Precisely: keeping every error

To keep going past errors, collect into Vec<Result<u32, _>>, or split the Results in two with partition: let (good, bad): (Vec<_>, Vec<_>) = results.into_iter().partition(|r| r.is_ok());. A ParseIntError says what kind of mistake it was, InvalidDigit or Empty, but not which text failed; to report the text, put it in the error yourself.

Experiment

Stop collecting at the first bad entry

collect into Result<Vec<u32>, _> asks for one Result at a time. It keeps each Ok value in a Vec. At the first Err it stops asking and returns that Err, so the map closure never runs for the items after it.

Why it matters Collect into a Result to get all the values or the first error, without reading past the error.

Predict how many read lines are printed. Then change "x" to "3" on line 2, and predict again.

fn main() {
    let typed = ["2", "x", "4"];
    let nights: Result<Vec<u32>, _> = typed
        .iter()
        .map(|t| {
            println!("read {}", t);
            t.parse::<u32>()
        })
        .collect();
    println!("{:?}", nights);
}
What to look for

The closure’s frame shows t as an address → "2" (len=1, data=0x…), then → "x". After read x, the closure returns from line 8, and the next stop is line 10: "4" is never read. nights shows Err({kind: core::num::error::IntErrorKind::InvalidDigit}), a longer form of the line that is printed. The run prints read 2, read x, then Err(ParseIntError { kind: InvalidDigit }). After the edit, the closure runs three times. nights shows Ok([2, 3, 4] (len=3, …)), and the run prints Ok([2, 3, 4]).

Expected output

read 2
read x
Err(ParseIntError { kind: InvalidDigit })

06 / In production

What goes wrong with iterators in real programs?

Four mistakes with iterators are common in real code.

Errors left out without a word. filter_map(|t| t.parse::<u32>().ok()) keeps the numbers that parse and leaves out the rest: ok() turns an Err into None, and filter_map drops each None. When bad input must be reported, a total computed this way is too low, and nobody is told. Collect into a Result instead, or count what was left out.

zip on lists of different lengths. zip ends when the shorter side ends, with no error. If one list lost an item, the last items of the other list are left out of the result. When two lists must match, compare their lengths before zipping them.

A side effect inside any, all, find, or position. These stop at the first answer, so a closure that also prints, logs, or counts runs for some items only. Do the effect in a for loop, and make the decision separately.

Collecting references where owned values are needed. cities.iter() gives &String, so collecting it into a Vec<String> gives error[E0277]: a value of type `Vec<String>` cannot be built from an iterator over elements of type `&String`, as Composition and data transformation showed for &str. Add .cloned() to copy each String, or use into_iter() when cities is no longer needed.

main.rs
let cities = ["Rome", "Split", "Vienna"];
let nights = [2, 3]; // one count is missing
for (city, n) in cities.iter().zip(nights.iter()) {
    println!("{}: {}", city, n);
}
// Rome: 2, Split: 3. Vienna is left out.

Report errors instead of filtering them out, compare lengths before zip, keep side effects out of closures that may stop early, and copy or hand over elements when the result must own them.

Experiment

Zip a city list with a shorter night list

zip gives a pair only while both sides still have items. nights ends after 3, so zip ends there, and the third city has no pair. Neither zip nor the loop treats this as an error.

Why it matters When two lists must have the same length, check it before you zip them.

Predict the lines printed and the total. Then change line 3 to let nights = [2, 3, 1]; and predict again.

fn main() {
    let cities = ["Rome", "Split", "Vienna"];
    let nights = [2, 3];
    let mut total = 0;
    for (city, n) in cities.iter().zip(nights.iter()) {
        println!("{}: {}", city, n);
        total += n;
    }
    println!("{} nights in all", total);
}
What to look for

The panel lists iter, the loop’s hidden zip, as {a: …, b: …}: the two iterators it reads from. The loop body runs for Rome and for Split. After Split: 3 and its total += n, the next stop is line 9, where total is 5: Vienna never reaches the loop body, and nothing reports it. The run prints Rome: 2, Split: 3, 5 nights in all. After the edit, the body runs three times, and the run prints Vienna: 1 and 6 nights in all.

Expected output

Rome: 2
Split: 3
5 nights in all

07 / Try it

Can you count the closure calls?

The chain below walks the nights from the last to the first, keeps the long stays, and stops after two. A counter shows how often filter’s closure runs.

main.rs
fn main() {
    let nights = vec![3, 1, 4, 2];
    let mut calls = 0;
    let picked: Vec<i32> = nights
        .iter()
        .rev()
        .filter(|n| {
            calls += 1;
            **n > 1
        })
        .take(2)
        .copied()
        .collect();
    let any_long = nights.iter().any(|n| *n >= 4);
    println!("{:?} {} {}", picked, calls, any_long);
}

What is printed? How many times does filter’s closure run, and why not four times?

Reveal the reasoning

[2, 4] 2 true. rev() passes on 2, 4, 1, 3, in that order. filter’s closure runs for 2, which passes, and take passes it on. It runs again for 4, which also passes. take(2) now has two items, so it gives None without asking filter for another one, and the closure never sees 1 or 3. calls is 2. copied() turns each &i32 into an i32 for the Vec<i32>. any reads 3, then 1, then 4, and stops there with true.

Practice tickets

  1. RUST-701 · Excursion prices stay the same after the fuel surcharge → Free
  2. RUST-702 · The long-walk warning names the day before the long one → Sign in · Pro
  3. RUST-703 · Typos vanish from the camper van’s kilometre total → Sign in · Pro

Keep exploring

Errors as values or exceptions →

Primary references