ZetCode

Rust iterators

last modified October 25, 2026

In this article we show how to work with iterators in Rust.

An iterator produces a sequence of values, one at a time. In Rust, an iterator is any type that implements the Iterator trait. The trait has one required method, next, which returns Some(item) while there are values left and None when the sequence is exhausted.

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // dozens of provided methods
}

On top of next, the trait provides dozens of methods. There are two kinds of them: adapters such as map and filter take an iterator and return a new iterator, while consumers such as sum, count, and collect drive the iterator to the end and produce a result. Iterator chains compile to code that is as fast as hand-written loops.

The examples were tested with Rust 1.97 and the 2024 edition. They are run as Cargo projects with cargo run -q.

The next method

We call next directly to see how an iterator works.

main.rs
fn main() {
    let words = ["sky", "cup", "war"];

    let mut it = words.iter();

    println!("{:?}", it.next());
    println!("{:?}", it.next());
    println!("{:?}", it.next());
    println!("{:?}", it.next());
}

The iter method of an array returns an iterator over references to its elements.

let mut it = words.iter();

The iterator must be mutable because next changes its internal state; it remembers how far it has got.

println!("{:?}", it.next());

Each call returns the next element wrapped in Some. After the three elements are used up, next returns None.

$ cargo run -q
Some("sky")
Some("cup")
Some("war")
None

iter, iter_mut, and into_iter

Collections offer three ways to iterate, depending on whether we want shared references, mutable references, or owned values.

main.rs
fn main() {
    let mut vals = vec![1, 2, 3, 4];

    for e in vals.iter() {
        print!("{e} ");
    }
    println!();

    for e in vals.iter_mut() {
        *e *= 10;
    }
    println!("{vals:?}");

    for e in vals.into_iter() {
        print!("{e} ");
    }
    println!();
}

The for loop calls next for us until it gets None.

for e in vals.iter() {

The iter method yields &i32 values. The vector is only borrowed and can be used after the loop. Writing for e in &vals is equivalent.

for e in vals.iter_mut() {
    *e *= 10;
}

The iter_mut method yields &mut i32 values, so we can change the elements in place. We dereference with * to modify the value behind the reference. The short form is for e in &mut vals.

for e in vals.into_iter() {

The into_iter method consumes the vector and yields owned i32 values. The short form is for e in vals. The vector is moved into the loop; using vals after the loop would be a compile-time error.

$ cargo run -q
1 2 3 4 
[10, 20, 30, 40]
10 20 30 40 

Iterators are lazy

Adapters do no work until a consumer asks for values.

main.rs
fn main() {
    let vals = [1, 2, 3, 4, 5];

    let it = vals.iter().map(|x| {
        println!("processing {x}");
        x * 2
    });

    println!("iterator created");

    let res: Vec<i32> = it.take(2).collect();
    println!("{res:?}");
}

The closure passed to map prints a message every time it is called.

let it = vals.iter().map(|x| {
    println!("processing {x}");
    x * 2
});

Creating the map adapter does not call the closure. It only stores the closure and the underlying iterator.

let res: Vec<i32> = it.take(2).collect();

The closure runs when collect pulls values through the chain. Because take(2) stops after two elements, only the first two values are processed; the remaining three are never touched. The compiler warns about adapters whose result is never used, since such code does nothing.

$ cargo run -q
iterator created
processing 1
processing 2
[2, 4]

map and filter

The map adapter transforms each element and filter keeps the elements that satisfy a predicate.

main.rs
fn main() {
    let vals = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let squares: Vec<i32> = vals.iter().map(|x| x * x).collect();
    println!("{squares:?}");

    let evens: Vec<&i32> = vals.iter().filter(|x| *x % 2 == 0).collect();
    println!("{evens:?}");

    let res: Vec<i32> = vals
        .iter()
        .filter(|&&x| x % 3 == 0)
        .map(|x| x * 10)
        .collect();
    println!("{res:?}");

    let words = ["1", "two", "3", "four", "5"];
    let nums: Vec<i32> = words.iter().filter_map(|s| s.parse().ok()).collect();
    println!("{nums:?}");
}

The example squares the values, selects even values, combines both adapters, and parses strings into numbers.

let evens: Vec<&i32> = vals.iter().filter(|x| *x % 2 == 0).collect();

The filter closure receives a reference to the item. Since the items are already &i32, the closure gets &&i32. The filtered elements remain references.

.filter(|&&x| x % 3 == 0)
.map(|x| x * 10)

A pattern in the closure parameter strips both references, so x is a plain i32. Arithmetic operators are also implemented for references, which is why x * 10 works on &i32 in map.

let nums: Vec<i32> = words.iter().filter_map(|s| s.parse().ok()).collect();

The filter_map adapter combines both operations: the closure returns an Option, None values are dropped, and Some values are unwrapped. Strings that are not numbers are skipped. Collecting the results is covered in detail in Rust collect.

$ cargo run -q
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[2, 4, 6, 8, 10]
[30, 60, 90]
[1, 3, 5]

Consumers

Consumers run the iterator to the end and return a single value.

main.rs
fn main() {
    let vals = [4, -2, 7, 0, 9, 3];

    let total: i32 = vals.iter().sum();
    let product: i64 = vals.iter().filter(|&&x| x != 0).map(|&x| x as i64).product();

    println!("sum: {total}");
    println!("product of non-zero: {product}");
    println!("count: {}", vals.iter().count());
    println!("min: {:?}", vals.iter().min());
    println!("max: {:?}", vals.iter().max());
    println!("last: {:?}", vals.iter().last());
    println!("second positive: {:?}", vals.iter().filter(|&&x| x > 0).nth(1));

    let empty: [i32; 0] = [];
    println!("max of empty: {:?}", empty.iter().max());
}

We compute several aggregate values from an array of integers.

let total: i32 = vals.iter().sum();
let product: i64 = vals.iter().filter(|&&x| x != 0).map(|&x| x as i64).product();

The sum and product methods are generic over the result type, so it must be annotated. Here the product is computed in i64 to leave more room before an overflow.

println!("min: {:?}", vals.iter().min());
println!("max: {:?}", vals.iter().max());

The min, max, last, and nth methods return an Option, because the iterator may be empty. The nth method counts from zero and consumes the skipped elements.

$ cargo run -q
sum: 21
product of non-zero: -1512
count: 6
min: Some(-2)
max: Some(9)
last: Some(3)
second positive: Some(7)
max of empty: None

fold and reduce

The fold method combines all elements into one value using an accumulator.

main.rs
fn main() {
    let vals = [1, 2, 3, 4, 5];

    let sum_sq = vals.iter().fold(0, |acc, x| acc + x * x);
    println!("sum of squares: {sum_sq}");

    let text = vals
        .iter()
        .fold(String::new(), |acc, x| format!("{acc}{x}-"));
    println!("{text}");

    let max = vals.iter().copied().reduce(|a, b| if a > b { a } else { b });
    println!("max: {max:?}");

    let words = ["an", "old", "falcon"];
    let longest = words
        .iter()
        .reduce(|a, b| if b.len() > a.len() { b } else { a });
    println!("longest: {longest:?}");
}

The fold method takes an initial value and a closure. The closure receives the accumulator and the next element and returns the new accumulator.

let sum_sq = vals.iter().fold(0, |acc, x| acc + x * x);

We compute the sum of squares: 1 + 4 + 9 + 16 + 25.

let text = vals
    .iter()
    .fold(String::new(), |acc, x| format!("{acc}{x}-"));

The accumulator can have a different type than the elements. Here we build a string.

let max = vals.iter().copied().reduce(|a, b| if a > b { a } else { b });

The reduce method has no initial value; it uses the first element as the accumulator. It returns None for an empty iterator. The copied adapter turns &i32 items into i32 values.

$ cargo run -q
sum of squares: 55
1-2-3-4-5-
max: Some(5)
longest: Some("falcon")

enumerate and zip

The enumerate adapter pairs elements with their indexes and zip pairs elements of two iterators.

main.rs
fn main() {
    let fruits = ["apple", "banana", "cherry"];
    let prices = [1.2, 0.5, 3.4];

    for (i, fruit) in fruits.iter().enumerate() {
        println!("{i}: {fruit}");
    }

    for (fruit, price) in fruits.iter().zip(prices.iter()) {
        println!("{fruit} costs {price}");
    }

    let nums = [1, 2, 3, 4, 5];
    let pairs: Vec<(i32, char)> = nums.into_iter().zip("abc".chars()).collect();
    println!("{pairs:?}");

    let (letters, codes): (Vec<char>, Vec<u32>) =
        "xyz".chars().map(|c| (c, c as u32)).unzip();
    println!("{letters:?} {codes:?}");
}

The example prints indexes, combines two arrays, and splits pairs back into two vectors.

for (i, fruit) in fruits.iter().enumerate() {

The enumerate adapter yields (index, item) tuples. We destructure the tuple directly in the loop header.

let pairs: Vec<(i32, char)> = nums.into_iter().zip("abc".chars()).collect();

The zip adapter stops as soon as either iterator is exhausted. The array has five numbers but the string only three characters, so we get three pairs.

let (letters, codes): (Vec<char>, Vec<u32>) =
    "xyz".chars().map(|c| (c, c as u32)).unzip();

The unzip consumer does the opposite: it splits an iterator of pairs into two collections.

$ cargo run -q
0: apple
1: banana
2: cherry
apple costs 1.2
banana costs 0.5
cherry costs 3.4
[(1, 'a'), (2, 'b'), (3, 'c')]
['x', 'y', 'z'] [120, 121, 122]

take, skip, and step_by

Several adapters select a part of the sequence.

main.rs
fn main() {
    let vals = [1, 3, 5, 6, 7, 8, 10, 11];

    let first: Vec<_> = vals.iter().take(3).collect();
    let rest: Vec<_> = vals.iter().skip(5).collect();
    println!("take(3): {first:?}");
    println!("skip(5): {rest:?}");

    let odd_prefix: Vec<_> = vals.iter().take_while(|&&x| x % 2 == 1).collect();
    let after: Vec<_> = vals.iter().skip_while(|&&x| x % 2 == 1).collect();
    println!("take_while odd: {odd_prefix:?}");
    println!("skip_while odd: {after:?}");

    let every_third: Vec<_> = vals.iter().step_by(3).collect();
    println!("step_by(3): {every_third:?}");

    let backwards: Vec<_> = vals.iter().rev().take(2).collect();
    println!("rev().take(2): {backwards:?}");
}

The take(n) adapter yields at most n elements and skip(n) drops the first n elements.

let odd_prefix: Vec<_> = vals.iter().take_while(|&&x| x % 2 == 1).collect();
let after: Vec<_> = vals.iter().skip_while(|&&x| x % 2 == 1).collect();

The take_while and skip_while adapters use a predicate instead of a count. They stop at the first element that fails the predicate; later odd values such as 7 and 11 are not considered by take_while, and they are kept by skip_while.

let every_third: Vec<_> = vals.iter().step_by(3).collect();
let backwards: Vec<_> = vals.iter().rev().take(2).collect();

The step_by adapter yields the first element and then every n-th element. The rev adapter reverses a double-ended iterator; combined with take it gives the last two elements.

$ cargo run -q
take(3): [1, 3, 5]
skip(5): [8, 10, 11]
take_while odd: [1, 3, 5]
skip_while odd: [6, 7, 8, 10, 11]
step_by(3): [1, 6, 10]
rev().take(2): [11, 10]

chain, flatten, and flat_map

These adapters join several sequences into one.

main.rs
fn main() {
    let a = [1, 2, 3];
    let b = [4, 5];

    let all: Vec<_> = a.iter().chain(b.iter()).collect();
    println!("{all:?}");

    let nested = vec![vec![1, 2], vec![], vec![3, 4, 5]];
    let flat: Vec<i32> = nested.into_iter().flatten().collect();
    println!("{flat:?}");

    let sentences = ["an old falcon", "a blue sky"];
    let words: Vec<&str> = sentences
        .iter()
        .flat_map(|s| s.split_whitespace())
        .collect();
    println!("{words:?}");

    let maybe = [Some(1), None, Some(3)];
    let present: Vec<i32> = maybe.into_iter().flatten().collect();
    println!("{present:?}");
}

The chain adapter yields all elements of the first iterator and then all elements of the second.

let flat: Vec<i32> = nested.into_iter().flatten().collect();

The flatten adapter removes one level of nesting. Empty inner vectors simply contribute nothing.

let words: Vec<&str> = sentences
    .iter()
    .flat_map(|s| s.split_whitespace())
    .collect();

The flat_map adapter is map followed by flatten. Each sentence is mapped to an iterator over its words, and the words of all sentences end up in one vector.

let present: Vec<i32> = maybe.into_iter().flatten().collect();

An Option behaves like a collection of zero or one elements. Flattening an iterator of options removes the None values.

$ cargo run -q
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
["an", "old", "falcon", "a", "blue", "sky"]
[1, 3]

Searching

Searching methods stop as soon as they know the answer.

main.rs
fn main() {
    let vals = [2, 4, 7, 8, 10];

    println!("any odd: {}", vals.iter().any(|x| x % 2 == 1));
    println!("all positive: {}", vals.iter().all(|&x| x > 0));

    println!("first > 5: {:?}", vals.iter().find(|&&x| x > 5));
    println!("first > 50: {:?}", vals.iter().find(|&&x| x > 50));

    println!("position of 8: {:?}", vals.iter().position(|&x| x == 8));
    println!("rposition even: {:?}", vals.iter().rposition(|x| x % 2 == 0));

    let words = ["sky", "Falcon", "cloud"];
    let first_upper = words
        .iter()
        .find_map(|w| w.chars().next().filter(|c| c.is_uppercase()));
    println!("{first_upper:?}");
}

The any method returns true if at least one element matches the predicate; all returns true if every element does. Both short-circuit.

println!("first > 5: {:?}", vals.iter().find(|&&x| x > 5));

The find method returns the first matching element as an Option.

println!("position of 8: {:?}", vals.iter().position(|&x| x == 8));
println!("rposition even: {:?}", vals.iter().rposition(|x| x % 2 == 0));

The position method returns the index of the first match; rposition searches from the end. Note the closure parameter: position passes the item itself (&i32), while find passes a reference to the item (&&i32).

let first_upper = words
    .iter()
    .find_map(|w| w.chars().next().filter(|c| c.is_uppercase()));

The find_map method applies a closure returning an Option and returns the first Some value. We find the first word that starts with an uppercase letter and return that letter.

$ cargo run -q
any odd: true
all positive: true
first > 5: Some(7)
first > 50: None
position of 8: Some(3)
rposition even: Some(4)
Some('F')

Infinite iterators

Since iterators are lazy, they can describe infinite sequences. We only take as many values as we need.

main.rs
use std::iter;

fn main() {
    let squares: Vec<u64> = (1..).map(|x| x * x).take(6).collect();
    println!("{squares:?}");

    let zeros: Vec<i32> = iter::repeat_n(0, 5).collect();
    println!("{zeros:?}");

    let powers: Vec<u32> = iter::successors(Some(1u32), |&x| x.checked_mul(10)).collect();
    println!("{powers:?}");

    let mut state = 1;
    let doubling = iter::from_fn(|| {
        state *= 2;
        if state < 100 { Some(state) } else { None }
    });
    println!("{:?}", doubling.collect::<Vec<_>>());

    let colors = ["red", "green", "blue"];
    let cycled: Vec<_> = colors.iter().cycle().take(7).collect();
    println!("{cycled:?}");
}

The example creates several sequences without a fixed end.

let squares: Vec<u64> = (1..).map(|x| x * x).take(6).collect();

The range 1.. has no upper bound. The take(6) adapter makes the chain finite.

let zeros: Vec<i32> = iter::repeat_n(0, 5).collect();

The iter::repeat function repeats a value forever; iter::repeat_n repeats it a fixed number of times.

let powers: Vec<u32> = iter::successors(Some(1u32), |&x| x.checked_mul(10)).collect();

The iter::successors function computes each element from the previous one. The sequence ends when the closure returns None; here this happens when multiplying by ten would overflow u32.

let mut state = 1;
let doubling = iter::from_fn(|| {
    state *= 2;
    if state < 100 { Some(state) } else { None }
});

The iter::from_fn function turns a closure into an iterator: each call of the closure produces the next element. The closure keeps its state in a captured variable.

let cycled: Vec<_> = colors.iter().cycle().take(7).collect();

The cycle adapter repeats a finite iterator endlessly.

$ cargo run -q
[1, 4, 9, 16, 25, 36]
[0, 0, 0, 0, 0]
[1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]
[2, 4, 8, 16, 32, 64]
["red", "green", "blue", "red", "green", "blue", "red"]

A custom iterator

We create our own iterator by implementing the Iterator trait.

main.rs
struct Fibonacci {
    curr: Option<u64>,
    next: Option<u64>,
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let res = self.curr?;
        self.curr = self.next;
        self.next = self.curr.and_then(|c| c.checked_add(res));
        Some(res)
    }
}

fn fibonacci() -> Fibonacci {
    Fibonacci { curr: Some(0), next: Some(1) }
}

fn main() {
    let first: Vec<u64> = fibonacci().take(12).collect();
    println!("{first:?}");

    let even_sum: u64 = fibonacci()
        .take_while(|&x| x < 4_000_000)
        .filter(|x| x % 2 == 0)
        .sum();
    println!("sum of even terms below four million: {even_sum}");

    println!("terms that fit into u64: {}", fibonacci().count());
    println!("largest: {:?}", fibonacci().last());
}

The Fibonacci struct generates the Fibonacci sequence until the values no longer fit into u64.

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let res = self.curr?;
        self.curr = self.next;
        self.next = self.curr.and_then(|c| c.checked_add(res));
        Some(res)
    }
}

We set the associated type Item and implement next. The two fields hold the current and the following term. When checked_add overflows, the stored value becomes None; the ? operator then ends the iteration two steps later, after the last two valid terms have been returned.

let even_sum: u64 = fibonacci()
    .take_while(|&x| x < 4_000_000)
    .filter(|x| x % 2 == 0)
    .sum();

By implementing just next, our type gets all the adapters and consumers of the trait for free. We sum the even terms below four million.

println!("terms that fit into u64: {}", fibonacci().count());
println!("largest: {:?}", fibonacci().last());

Because the sequence ends on overflow, we can also count its terms and get the last one.

$ cargo run -q
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
sum of even terms below four million: 4613732
terms that fit into u64: 94
largest: Some(12200160415121876738)

IntoIterator

The for loop accepts any type that implements IntoIterator. Every iterator implements it, and so can our own collection types.

main.rs
struct Countdown {
    from: u32,
}

impl Iterator for Countdown {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        if self.from == 0 {
            return None;
        }
        self.from -= 1;
        Some(self.from + 1)
    }
}

struct Playlist {
    songs: Vec<String>,
}

impl<'a> IntoIterator for &'a Playlist {
    type Item = &'a String;
    type IntoIter = std::slice::Iter<'a, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.songs.iter()
    }
}

fn main() {
    for n in (Countdown { from: 5 }) {
        print!("{n} ");
    }
    println!("liftoff");

    let pl = Playlist {
        songs: vec!["Intro".to_string(), "Theme".to_string(), "Outro".to_string()],
    };

    for song in &pl {
        println!("{song}");
    }
}

The Countdown iterator counts down to one. The Playlist type is a collection that can be looped over by reference.

for n in (Countdown { from: 5 }) {

An iterator can be used directly in a for loop. The parentheses are required here, because a struct literal is not allowed directly in the loop header.

impl<'a> IntoIterator for &'a Playlist {
    type Item = &'a String;
    type IntoIter = std::slice::Iter<'a, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.songs.iter()
    }
}

We implement IntoIterator for a reference to Playlist. Instead of writing a new iterator type, we reuse the slice iterator of the inner vector. The 'a lifetime ties the yielded references to the borrowed playlist; lifetimes are explained in Rust lifetimes.

for song in &pl {

Thanks to the implementation, &pl can be used in a for loop, just like &vec.

$ cargo run -q
5 4 3 2 1 liftoff
Intro
Theme
Outro

Peekable iterators

The peekable adapter lets us look at the next element without consuming it.

main.rs
fn main() {
    let data = [1, 1, 1, 2, 2, 3, 1, 1];

    let mut it = data.iter().peekable();
    let mut runs = Vec::new();

    while let Some(&x) = it.next() {
        let mut n = 1;
        while it.next_if_eq(&&x).is_some() {
            n += 1;
        }
        runs.push((x, n));
    }

    println!("{runs:?}");

    let tokens = ["12", "+", "7", "-", "3"];
    let mut it = tokens.iter().peekable();
    while let Some(tok) = it.next() {
        match it.peek() {
            Some(next) => println!("{tok} is followed by {next}"),
            None => println!("{tok} is the last token"),
        }
    }
}

We compress runs of equal values and inspect a token stream.

while let Some(&x) = it.next() {
    let mut n = 1;
    while it.next_if_eq(&&x).is_some() {
        n += 1;
    }
    runs.push((x, n));
}

The next_if_eq method consumes the next element only if it is equal to the given value. We use it to count how many times a value repeats. The result is a run-length encoding of the data.

match it.peek() {
    Some(next) => println!("{tok} is followed by {next}"),
    None => println!("{tok} is the last token"),
}

The peek method returns a reference to the next element, or None at the end. This is useful in parsers that need one token of lookahead.

$ cargo run -q
[(1, 3), (2, 2), (3, 1), (1, 2)]
12 is followed by +
+ is followed by 7
7 is followed by -
- is followed by 3
3 is the last token

max_by_key and scan

The _by_key methods compare elements by a derived key.

main.rs
#[derive(Debug)]
struct City {
    name: &'static str,
    population: u32,
}

fn main() {
    let cities = [
        City { name: "Bratislava", population: 475_000 },
        City { name: "Kosice", population: 229_000 },
        City { name: "Presov", population: 82_000 },
        City { name: "Zilina", population: 80_000 },
    ];

    let largest = cities.iter().max_by_key(|c| c.population).unwrap();
    let smallest = cities.iter().min_by_key(|c| c.population).unwrap();
    println!("largest: {}", largest.name);
    println!("smallest: {}", smallest.name);

    let shortest_name = cities.iter().min_by_key(|c| c.name.len()).unwrap();
    let last_short = cities.iter().rev().min_by_key(|c| c.name.len()).unwrap();
    println!("shortest name: {}", shortest_name.name);
    println!("shortest name from the end: {}", last_short.name);

    let total: u32 = cities.iter().map(|c| c.population).sum();
    println!("total population: {total}");

    let running: Vec<u32> = cities
        .iter()
        .scan(0, |acc, c| {
            *acc += c.population;
            Some(*acc)
        })
        .collect();
    println!("running totals: {running:?}");
}

We work with a list of cities.

let largest = cities.iter().max_by_key(|c| c.population).unwrap();

The max_by_key and min_by_key methods return the element with the largest or smallest key. The struct itself does not need to be comparable.

let shortest_name = cities.iter().min_by_key(|c| c.name.len()).unwrap();
let last_short = cities.iter().rev().min_by_key(|c| c.name.len()).unwrap();

Three names have six letters. When several elements are equally minimal, min_by_key returns the first one; for ties, max_by_key returns the last one. Reversing the iterator gives the last of the shortest names.

let running: Vec<u32> = cities
    .iter()
    .scan(0, |acc, c| {
        *acc += c.population;
        Some(*acc)
    })
    .collect();

The scan adapter is like fold, but it yields the intermediate states. We get running totals of the population.

$ cargo run -q
largest: Bratislava
smallest: Zilina
shortest name: Kosice
shortest name from the end: Zilina
total population: 866000
running totals: [475000, 704000, 786000, 866000]

Source

Iterator trait documentation

The Rust Programming Language: Processing a Series of Items with Iterators

In this article we have worked with iterators in Rust.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Rust tutorials.