Rust— IntoIterator Link to heading
Rust standard library provides a trait called Iterator that turbo-charges Rust with functional paradigm with various built-in methods. There is a related trait called IntoIterator as well, which can be confusing to the Rust novices. Today, let’s have a look at these two traits and see when to use IntoIterator trait.
Iterator trait is the core trait for iterating over collections of items, such as an array, slice, vector, hashmap, etc. It requires next() method to be defined, from which a few dozens of powerful functional methods are provided.
pub trait Iterator {
type Item;
// Required method
fn (&mut self) -> Option<<Self>::Item>;
// Provided methods
...
}
On the other hand, IntoIterator is a very simple trait that has just a single method into_iter(), which returns an Iterator.
pub trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
// Required method
fn into_iter(self) -> Self::IntoIter;
}
Moreover, every Iterator implements IntoIterator.
impl<I: Iterator> IntoIterator for I
So then, what is the use case of IntoIterator? There are two use cases
- any struct that may not be an iterator by itself but can be easily converted to an iterator
- to easily create an iterator over different types, i.e.,
T,&T, or&mut T
Main use case Link to heading
Let’s look at the first case. A slice &[T] is a simple fat pointer that stores the address and the number of elements. Slice type by itself is not an iterator. Similarly for Vec<T>. The following code will output compilation error because they do not implement Iterator trait by themselves.
use std::ops::Add;
fn get_sum<I>(xs: I) -> i32
where
I: Iterator,
i32: Add<I::Item, Output = i32>,
{
xs.fold(0, |acc, x| acc + x)
}
fn main() {
let x = &[0, 1, 2];
let y = vec![0, 1, 2];
let z = 0..3;
println!("{}", get_sum(x));
println!("{}", get_sum(y));
println!("{}", get_sum(z));
}
error[E0277]: `&[{integer}; 3]` is not an iterator
--> src/main.rs:15:28
|
15 | println!("{}", get_sum(x));
| ------- ^ `&[{integer}; 3]` is not an iterator
| |
| required by a bound introduced by this call
|
= help: the trait `Iterator` is not implemented for `&[{integer}; 3]`
note: required by a bound in `get_sum`
--> src/main.rs:5:8
|
3 | fn get_sum<I>(xs: I) -> i32
| ------- required by a bound in this function
4 | where
5 | I: Iterator,
| ^^^^^^^^ required by this bound in `get_sum`
error[E0277]: `Vec<{integer}>` is not an iterator
--> src/main.rs:16:28
|
16 | println!("{}", get_sum(y));
| ------- ^ `Vec<{integer}>` is not an iterator
| |
| required by a bound introduced by this call
|
= help: the trait `Iterator` is not implemented for `Vec<{integer}>`
note: required by a bound in `get_sum`
--> src/main.rs:5:8
|
3 | fn get_sum<I>(xs: I) -> i32
| ------- required by a bound in this function
4 | where
5 | I: Iterator,
| ^^^^^^^^ required by this bound in `get_sum`
However, it would be very convenient and logical to use a slice or a vector as an iterator, and this is precisely why both types implement IntoIterator trait. Therefore, simply replacing Iterator with IntoIterator and adding .into_iter() in the code above will compile
use std::ops::Add;
fn get_sum<I>(xs: I) -> i32
where
I: IntoIterator,
i32: Add<I::Item, Output = i32>,
{
xs.into_iter().fold(0, |acc, x| acc + x)
}
fn main() {
let x = &[0, 1, 2];
let y = vec![0, 1, 2];
let z = 0..3;
println!("{}", get_sum(x));
println!("{}", get_sum(y));
println!("{}", get_sum(z));
}
Of course, we could have also done get_sum(x.iter()) with the first implementation, but that would be less flexible and more verbose on the user end.
Prefer a function that accepts
IntoIteratoroverIterator.
Secondary use case Link to heading
The second use case is much more subtle. Say we want to create an iterator from a Vec<T>. There are three possible Iterator variants depending on its Item’s type
Iterator<Item = T>
Iterator<Item = &T>
Iterator<Item = &mut T>
How can we easily choose which variant to produce? Well, that is where IntoIterator comes into play. In the Rust standard library, Vec<T> implements three different variations of IntoIterator:
impl<T, A: Allocator> IntoIterator for Vec<T, A>
impl<'a, T, A: Allocator> IntoIterator for &'a Vec<T, A>
impl<'a, T, A: Allocator> IntoIterator for &'a mut Vec<T, A>
That is, we can easily choose which version of Iterator is passed, as in the following code example
struct Foo {
x: i32
}
fn iter(xs: impl IntoIterator<Item = Foo>) {}
fn iter_ref<'a>(xs: impl IntoIterator<Item = &'a Foo>) {}
fn iter_mut<'a>(xs: impl IntoIterator<Item = &'a mut Foo>) {}
fn main() {
let x = vec![Foo{x: 0}, Foo{x: 1}, Foo{x: 2}];
iter_ref(&x);
iter_mut(&mut x);
iter(x);
}
Of course, we could have also done x.into_iter(), x.iter(), and x.iter_mut(), but again, that would be more verbose on the user end.
References Link to heading
- std::iter - Rust - Composable external iteration.
- Iterator in std::iter - Rust - A trait for dealing with iterators.
- IntoIterator in std::iter - Rust - Conversion into an
Iterator. impl Iteratorvsimpl IntoIterator, which is idomatic? - I usually use impl Iterator when I want to return an iterator or consume an iterator. But I found some people are using…