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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
#[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
The Rust Programming Language: Processing a Series of Items with Iterators
In this article we have worked with iterators in Rust.
Author
List all Rust tutorials.