Iterators
The iterator pattern allows you to perform some task on a sequence of items in turn. Iterators provide a way to traverse, transform, or consume elements efficiently without needing to manually manage indices or loops.
Rust iterators are lazy by default, meaning they don’t perform operations until explicitly consumed.
Closures and iterators are Rust features inspired by functional programming language ideas. They contribute to Rust’s capability to clearly express high-level ideas at low-level performance. The implementations of closures and iterators are such that runtime performance is not affected. This is part of Rust’s goal to strive to provide zero-cost abstractions. - Comparing Performance: Loops vs. Iterators
Iterator Trait
An iterator is any type that implements the Iterator trait. The Iterator trait has one required method:
fn next(&mut self) -> Option<Self::Item>;
next
returns the next item in the sequence wrapped in Some
or None
if the iterator is exhausted.
Advances the iterator and returns the next value.
Returns
None
when iteration is finished. Individual iterator implementations may choose to resume iteration, and so callingnext()
again may or may not eventually start returningSome(Item
) again at some point.
Example:
fn main() {
let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());
}
Output:
Some(1)
Some(2)
Some(3)
None
Consumer Adapters
The Iterator trait has a number of different methods with default implementations provided by the standard library. Methods that call next are called consuming adapters, because calling them uses up the iterator.
Some of commonly used consumer adapters methods: count
, sum
, fold
, product
, collect
, etc. You can find more of the provided method here: https://doc.rust-lang.org/std/iter/trait.Iterator.html#provided-methods.
Examples
Sum
fn sum<S>(self) -> S
where
Self: Sized,
S: Sum<Self::Item>,
-
Sums the elements of an iterator.
-
Takes each element, adds them together, and returns the result.
-
An empty iterator returns the zero value of the type.
-
Example:
fn main() {
let numbers = vec![1, 2, 3];
let iter = numbers.iter();
let x: i32 = iter.sum();
println!("{:?}", x); // 6
}
Fold
fn fold<B, F>(self, init: B, f: F) -> B
where
Self: Sized,
F: FnMut(B, Self::Item) -> B,
-
Folds every element into an accumulator by applying an operation, returning the final result.
-
fold()
takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element. The closure returns the value that the accumulator should have for the next iteration. -
The initial value is the value the accumulator will have on the first call.
-
After applying this closure to every element of the iterator,
fold()
returns the accumulator. -
Example:
fn main() {
let numbers = vec![1, 2, 3];
let iter = numbers.iter();
let x: i32 = iter.fold(0, |acc, &x| acc + x * x);
println!("{:?}", x); // 14
}
Iterator Adapters
Iterator adapters are methods defined on the Iterator trait that don’t consume the iterator. Instead, they produce different iterators by changing some aspect of the original iterator.
Some of commonly used iterator adapters method: map
, filter
, take
, skip
, enumerate
, etc. Again, you can find more of the provided method here: https://doc.rust-lang.org/std/iter/trait.Iterator.html#provided-methods.
Examples
Map
fn map<B, F>(self, f: F) -> Map<Self, F> ⓘ
where
Self: Sized,
F: FnMut(Self::Item) -> B,
-
Takes a closure and creates an iterator which calls that closure on each element.
-
map()
transforms one iterator into another, by means of its argument: something that implementsFnMut
. It produces a new iterator which calls this closure on each element of the original iterator. -
If you are good at thinking in types, you can think of
map()
like this: If you have an iterator that gives you elements of some typeA
, and you want an iterator of some other typeB
, you can usemap()
, passing a closure that takes anA
and returns aB
.fn main() {
let numbers = vec![1, 2, 3];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6]
}
Filter
fn filter<P>(self, predicate: P) -> Filter<Self, P> ⓘ
where
Self: Sized,
P: FnMut(&Self::Item) -> bool,
-
Creates an iterator which uses a closure to determine if an element should be yielded.
-
Given an element the closure must return
true
orfalse
. The returned iterator will yield only the elements for which the closure returnstrue
. -
Example:
fn main() {
let numbers = vec![1, 2, 3];
// Filter number that != 2
let mut iter = numbers.iter().filter(|x| **x != 2);
println!("{:?}", iter.next()); // Some(1)
println!("{:?}", iter.next()); // Some(3)
println!("{:?}", iter.next()); // None
}
The Power of collect()
fn collect<B>(self) -> B
where
B: FromIterator<Self::Item>,
Self: Sized,
The collect
method is part of the Iterator
trait and is used to transform an iterator into a collection. It relies on the FromIterator
trait to determine how to build the target type.
Example of Vec::from_iter
:
fn main() {
let five_fives = std::iter::repeat(5).take(5);
let v = Vec::from_iter(five_fives);
println!("{:?}", v); // [5, 5, 5, 5, 5]
}
collect()
can take anything iterable, and turn it into a relevant collection. This is one of the more powerful methods in the standard library, used in a variety of contexts.
The most basic pattern in which collect()
is used is to turn one collection into another. You take a collection, call iter
on it, do a bunch of transformations, and then collect()
at the end.
Example of collect
:
fn main() {
fn take_vec(v: Vec<i32>) {
println!("{:?}", v) // [2, 4, 6]
}
let numbers = vec![1, 2, 3];
let doubled = numbers.iter().map(|x| x * 2).collect();
take_vec(doubled); // Compiler infers the target type as Vec<i32>
}
- So in here Rust will check for the target type when
collect
is called. - As we can see the function
take_vec
is expecting a typeVec<i32>
. - Rust compiler then will infer the target of
collect
isVec<i32>
. - And because
Vec
hasFromIterator
trait, it will callVec::from_iter
function and produce the result.
Why collect
Fails Sometimes?
There are various reason that sometimes collect
cannot built the targeted type, some's are:
-
Ambiguous Target Type: If the target type is not clear from the context.
-
Type Mismatch: If the iterator produces items that cannot be converted to the target type.
-
Unsupported Target Type: If the target type doesn’t implement FromIterator.
Though, we can implement
FromIterator
if we have our own custom type like this:fn main() {
use std::iter::FromIterator;
#[derive(Debug)]
struct MyType(Vec<i32>);
impl FromIterator<i32> for MyType {
fn from_iter<I: IntoIterator<Item = i32>>(iter: I) -> Self {
MyType(iter.into_iter().collect())
}
}
let numbers = vec![1, 2, 3];
let result: MyType = numbers.into_iter().collect();
println!("{:?}", result); // MyType([1, 2, 3])
}
iter
vs iter_mut
vs into_iter
.iter()
- Returns an iterator of references (
&T
) over the elements of the collection. - The collection is not consumed.
- Cannot modify the original collection’s elements (unless combined with
.iter_mut()
).
Example:
fn main() {
let numbers = vec![1, 2, 3];
let sum: i32 = numbers.iter().sum();
println!("Sum: {:?}", sum); // Sum: 6
println!("Vec: {:?}", numbers); // Vec: [1, 2, 3]
}
.iter_mut()
- Returns an iterator of mutable references (
&mut T
) over the elements of the collection. - For modifying elements of a collection in place.
Example:
fn main() {
let mut numbers = vec![1, 2, 3];
println!("Vec: {:?}", numbers); // Vec: [1, 2, 3]
numbers.iter_mut().for_each(|x| *x *= 2);
println!("Vec: {:?}", numbers); // Vec: [2, 4, 6]
}
.into_iter()
- Consumes the collection and returns an iterator of owned values (
T
). - Moving ownership of each element.
- After calling
.into_iter()
, the original collection cannot be used anymore because ownership is transferred to the iterator.
Exmaple:
fn main() {
let numbers = vec![1, 2, 3];
let doubled: Vec<i32> = numbers.into_iter().map(|x| x * 2).collect();
println!("Vec: {:?}", doubled); // Vec: [2, 4, 6]
println!("Vec: {:?}", numbers); // error[E0382]: borrow of moved value: `numbers`
}