Iterators
The Iterator
and IntoIterator
Traits
Term: An iterator is any value that implements the
std::iter::Iterator
trait. Put simply, an iterator is value that produces a sequence of values.
Term: The values an iterator produces are called items.
Term: The code that receives an iterator's items is called a consumer.
The heart of the Iterator
trait is defined as:
#![allow(unused)] fn main() { trait Iterator { type Item; // the type of value the iterator produces fn next(&mut self) -> Option<Self::Item>; // ... a whole bunch of default methods } }
If there's a natural way to iterator over some type, it can implement std::iter::IntoIterator
, whose into_iter
method takes a value and returns an iterator over it:
#![allow(unused)] fn main() { trait IntoIterator where Self::IntoIter::Item == Self::Item { type Item; // the type of value the iterator produces type IntoIter: Iterator; // the type of the iterator value itself fn into_iter(self) -> Self::IntoIter; } }
Term: Any type that implements
std::iter::IntoIterator
is called an iterable.
Under the hood, every for loop is just shorthand for calls to IntoIterator
and Iterator
methods:
#![allow(unused)] fn main() { let elems = vec!["antimony", "arsenic", "aluminum", "selenium"]; // Iteration using a for loop.. println!("There's:"); for element in &elems { println!("- {}", element); } // Is actually just... println!("Again! There's:"); let mut iterator = (&elems).into_iter(); while let Some(element) = iterator.next() { println!("- {}", element); } }
Implicit Behavior: All iterators automatically implement
IntoIterator
, with aninto_iter
method that simply returns the iterator.
Creating Iterators
iter
and iter_mut
Methods
Most collection types provide iter
and iter_mut
methods that return the natural iterators over the type, producing a shared or mutable reference to each item. The same applies to slices like &[T]
and &str
too.
IntoIterator
Implementations
There are three main implementations of IntoIterator
.
1. Shared Reference
Idiom: Given a shared reference to a collection,
into_iter
returns an iterator that produces shared references to its items.#![allow(unused)] fn main() { for element in &collection { ... } }
2. Mutable Reference
Idiom: Given a mutable reference to a collection,
into_iter
returns an iterator that produces mutable references to the items.#![allow(unused)] fn main() { for element in &mut collection { ... } }
3. By Value
Idiom: When passed a collection by value,
into_iter
returns an iterator that takes ownership of the collection and returns items by value; the items' ownership moves from the collection to the consumer, and the original collection is consumed in the process.#![allow(unused)] fn main() { for element in collection { ... } }
Not every type provides all three iterator implementations.
Slices implement two of the three IntoIterator
variants; since they don't own their elements, there is no "by value" case.
Use Case:
IntoIterator
can be useful in generic code: you can use a bound likeT: IntoIterator
to restrict the tyep variableT
to types that can be iterator over. Or, you can writeT: IntoIterator<Item=U>
to further require the iteration to produce a particular typeU
. For instance, we can create adump
function that receives an iterable whose items implement theDebug
trait:#![allow(unused)] fn main() { use std::fmt::Debug; fn dump<T, U>(t: T) where T: IntoIterator<Item=U>, U: Debug { for u in t { println!("{:?}", u); } } dump(vec!["garbage", "rubbish", "waste"]); }
drain
Methods
A lot of collection types provide a drain
method that takes a mutable reference to the collection and returns an iterator that passes ownership of each element to the consumer.
#![allow(unused)] fn main() { use std::iter::FromIterator; let mut outer = "Earth".to_string(); let inner = String::from_iter(outer.drain(1..4)); println!("outer: {}", outer); println!("inner: {}", inner); }
If you need to drain an entire sequence, use the full range, ..
, as the argument.
Other Iterator Sources
Iterator Adapters
Term: Given an iterator, the
Iterator
trait provides a huge selection of methods called adapters that consume one iterator and build a new one.
map
and filter
A map
iterator passes each item to its closure by value, and in turn, passes along ownership of the closure's result to its consumer.
A filter
iterator passes each item to its closure by shared reference, retaining ownership in case the item is selected to be passed on to its consumer.
Concept: Calling an adapter on an iterator doesn't consume any items; it just returns a new iterator. The only way to actually get values is to call
next
(or some other indirect method, likecollect
, in which case no work takes place untilcollect
starts callingnext
) on the iterator.
filter_map
and flat_map
The filter_map
adapter is similar to map
, except that it lets its closure either transform the item into a new item or drop the item from the iteration. Thus, it's a bit like a combination of filter
and map
.
Use Case: The
filter_map
adapter is best in situations when the best way to decide whether to include an item in the iteration is to actually try to process it.
The flat_map
iterator produces the concatenation of the sequences the closure returns.
scan
The scan
adapter resembles map
, except that the closure is given a mutable value it can consult, and has the option of terminating the iteration early. The closure must return an Option
, which the scan
iterator takes as its next item.
take
and take_while
The Iterator
trait's take
and take_while
adapters let you end an iteration after a certain number of items, or when a closure decides to cut things off.
Both take
and take_while
take ownership of an iterator and return a new iterator that passes along items from the first one, possible ending the sequence earlier.
skip
and skip_while
The Iterator
trait's skip
and skip_while
methods are the complement of take
and take_while
: they drop a certain number of items from the beginning of an iteration, or drop items until a closure finds one acceptable, and then pass the remaining items through unchanged.
Use Case: One common use for
skip
is to skip the command name when iterating over a programs command-line arguments:#![allow(unused)] fn main() { for arg in std::end::args().skip(1) { println!("arg: {}", arg); } }
peekable
A peekable iterator lets you peek at the next item that will be produced without actually consuming it. Almost any iterator can be turned into a peekable iterator by calling the Iterator
trait's peekable
method.
Calling peek
tries to draw the next item from the underlying iterator, and if there is one, caches it until the next call to next
.
Use Case: Peekable iterators are essential when you can't decide how many items to consume from an iterator until you've gone too far. For example, if you're parsing numbers from a stream of characters, you can't decide where the number ends until you've seen the first non-number character:
#![allow(unused)] fn main() { use std::iter::Peekable; fn parse_number<I>(tokens: &mut Peekable<I>) -> u32 where I: Iterator<Item=char> { let mut n = 0; loop { match tokens.peek() { Some(r) if r.is_digit(10) => { n = n * 10 + r.to_digit(10).unwrap(); } _ => return n } tokens.next(); } } let mut chars = "10212980".chars().peekable(); println!("{}", parse_number(&mut chars)); }
fuse
The fuse
adapter takes any iterator and turns it into one that will definitely continue to return None
once it has done so the first time.
Use Case: The
fuse
adapter is most useful in generic code that needs to work with iterators of an uncertain origin.
Reversible Iterators and rev
Some iterators are able to draw items from both ends of the sequence. You can reverse these iterators by using the rev
adapter.
Most iterator adapters, if applied to a reversible iterator, return another reversible iterator.
inspect
The inspect
adapter is handy for debugging pipelines of iterator adapters, but is rarely used in production code. It applies a closure to a shared reference to each item, and then passes the item through. The closure can't affect the items, but it can do things like print them or make assertions about them.
chain
The chain
adapter appends one iterator to another (think of the concat
operator in RxJS). A chain
iterator keeps track of whether each of the two underlying iterators has return None
, and directs next
and next_back
calls to one or the other as appropriate.
#![allow(unused)] fn main() { let v: Vec<_> = (1..4).chain(4..6).collect(); println!("{:?}", v); }
enumerate
The Iterator
trait's enumerate
adapter attaches a running index to the sequence, taking an iterator that produces items A, B, C, ...
and returning an iterator that produces pairs (0, A), (1, B), (2, C), ...
.
zip
The zip
adapter combines two iterators into a single iterator that produces pairs holding one value from each iterator. The zipped iterator ends when either of the two underlying iterators ends.
by_ref
An iterator's by_ref
method borrows a mutable reference to the iterator, so that you can apply adaptors to the reference. When you're done consuming items from these adaptors, you drop them, the borrow ends, and you regain access to the original iterator.
by_ref
essentially provides a mechanism for starting and stopping iterators as needed.
Concept: When you call an adapter on a mutable reference to an iterator, the adapter takes ownership of the reference, not the iterator itself.
cloned
The cloned
adapter takes an iterator that produces references, and returns an iterator that produces values cloned from those references.
cycle
The cycle
adapter returns an iterator that endlessly repeats the sequence produced by the underlying iterator. The underlying iterator must implement std::clone::Clone
, so that cycle
can save its initial state and reuse it each time the cycle starts again.
#![allow(unused)] fn main() { use std::iter::{once, repeat}; let fizzes = repeat("").take(2).chain(once("fizz")).cycle(); let buzzes = repeat("").take(4).chain(once("buzz")).cycle(); let fizzes_buzzes = fizzes.zip(buzzes); let fizz_buzz = (1..100).zip(fizzes_buzzes) .map(|tuple| match tuple { (i, ("", "")) => i.to_string(), (_, (fizz, buzz)) => format!("{}{}", fizz, buzz) } ); for line in fizz_buzz { println!("{}", line); } }
Consuming Iterators
Simple Accumulation: count
, sum
, product
The count
method draws items from an iterator until it returns None
, and tells you how many it got.
The sum
and product
methods compute the sum or product of the iterator's items, which must be integers or floating-point numbers.
max
, min
The max
and min
methods on Iterator
return the least or greatest item the iterator produces. The iterator's item type must implement std::cmp::Ord
, so that items can be compared with each other.
An implication of the Ord
bound is that these methods can't be used with floating-point values.
max_by
, min_by
The max_by
and min_by
methods return the maximum or minimum item an iterator produces, as determined by a comparator function you provide.
max_by_key
, min_by_key
The max_by_key
and min_by_key
methods on Iterator
let you select the maximum or minimum item as determined by a closure applied to each item.
Comparing Item Sequences
any
and all
The any
and all
methods apply a closure to each item the iterator produces, and return true
if the closure returns true
for any or all items, respectively.
These methods consume only as many items as they need to determine the answer.
position
, rposition
, and ExactSizeIterator
The position
method applies a closure to each item from the iterator and returns the index of the first item for which the closure returns true
as an Option<bool>
.
The rposition
methods does the same thing but in reverse.
Term: An exact-size iterator is one that implements the
std::iter::ExactSizeIterator
trait:#![allow(unused)] fn main() { trait ExactSizeIterator: Iterator { fn len(&self) -> usize { ... } fn is_empty(&self) -> bool { ... } } }
fold
The fold
method is a very general tool for accumulating some sort of result over the entire sequence of items an iterator produces.
nth
The nth
method takes an index n
, skips that many items from the iterator, and returns the next item, or None
if the sequence ends before that point.
Calling .nth(0)
is equivalent to calling .next()
.
It doesn't take ownership of the iterator the way an adapter does, so you can call it many times.
last
The last
method consumes items until the iterator returns None
, and then returns the last item. If the iterator produces no items, then last
returns None
.
Tip: If you have a reversible iterator and just want the last item, use
iter.rev().next()
instead.
find
The find
method draws items from an iterator, returning the first item for which the given closure returns true
, or None
if the sequence ends before a suitable item is found.
Building Collections: collect
and FromIterator
An iterator's collect
method can build any kind of collection from the Rust's standard library, as long as the iterator produces a suitable item type. The return type of collect
is its type parameter.
When some collection type like Vec
or HashMap
knows how to construct itself from an iterator, it implements the std::iter::FromIterator
trait, for which collect
is a method:
#![allow(unused)] fn main() { trait FromIterator<A>: Sized { fn from_iter<T: IntoIterator<Item=A>>(iter: T) -> Self; } }
Concept: If a collection type implements
FromIterator<A>
, then its static methodfrom_iter
builds a value of that type from an iterable producing items of typeA
.
The size_hint
method of Iterator
returns a lower bound and optional upper bound on the number of items the iterator will produce.
The Extend
Trait
If a type implements the std::iter::Extend
trait, then its extend
method adds an iterable's items to the collection.
All of the standard collections implement Extend
. Arrays and slices do not have this method because they are not of fixed length.
partition
The partition
method divides an iterator's items among two collections, using a closure to decide where each item belongs.
Whereas collect
requires its result type to implement FromIterator
, partition
instead requires std::default::Default
and std::default::Extend
.