Итераторы играют важную роль в стандартной библиотеке языка Rust при работе с коллекциями. В частности, итераторы позволяют перебрать объект, например, получить из массива элементы. По своей сути итератор — это объект, который генерирует последовательность значений. И эту последовательность можно удобно использовать в циклах или других операциях. Стандартная библиотека Rust предоставляет широкий спектр итераторов, адаптированных для разных типов данных, включая векторы, строки, хеш-таблицы и многое другое. Кроме того, мы сами можем создавать собственные итераторы в соответствии с конкретными требованиями.
Итератор поставляется с набором методов, которые позволяют нам просматривать коллекцию элементов один за другим, из которых отметим прежде всего
метод next(), который позволяет извлечь следующий элемент в последовательности, оборачивая его в значение перечисления Option. Если последовательность действительно имеет элемент, то метод возвратит его в виде Some(item),
где item - это извлекаемый элемент. Но если мы достигли конца последовательности, то есть элементов больше нет, то метод возвращает None.
Возможность итерации по объекту предоставляет специальный трейт - Iterator:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// остальные методы
}
Item представляет ассоциированный тип, который будет применяться в качестве типа генерируемых итератором элементов.
Метод next() собственно генерирует значения типа Item и возвращает их в виде Option<Self::Item>.
Рассмотрим простейший пример реализации итератора и его метода next():
struct Counter {
count: u32
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < 10 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let mut counter = Counter { count: 0 };
for number in &mut counter {
println!("Counter: {}", number);
}
}
Здесь определена структура Counter, которая в переменной count хранит некоторое значение. Структура Counter применяет трейт Iterator. В качестве ассоциированного типа
Item применяется тип u32 (то есть 32-разрядные целые числа без знака) - это тип возвращаемых
итератором значений.
Реализация метода next() просто увеличивает значение count и возвращает это значение в виде Some(self.count). Допустим, в данном случае нам надо возвратить 10 чисел. Когда мы возвратим все числа,
то, чтобы показать, что перебор закончен, возвращаем None. В итоге мы получим следующий консольный вывод:
Counter: 1 Counter: 2 Counter: 3 Counter: 4 Counter: 5 Counter: 6 Counter: 7 Counter: 8 Counter: 9 Counter: 10
Другой пример - определим генератор простых чисел:
struct PrimeGenerator {
current: u32
}
impl Iterator for PrimeGenerator {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
loop {
self.current += 1;
if is_prime(self.current) {
return Some(self.current);
}
}
}
}
fn is_prime(num: u32) -> bool {
if num <= 1 { return false; }
for i in 2..(num / 2 + 1) {
if num % i == 0 { return false; }
}
true
}
fn main() {
let prime_sequence = PrimeGenerator { current: 1 };
for prime in prime_sequence.take(10) {
println!("{}", prime);
}
}
Здесь для типа PrimeGenerator реализуем итератор таким образом, чтобы в методе next() в бесконечном цикле возвращались простые числа. Поскольку этот генератор бесконечен, то в функции
main при переборе чисел с помощью функции take() берем только 10 первых простых чисел. Консольный вывод:
2 3 5 7 11 13 17 19 23 29
Кроме трейта Iterator следует отметить еще один тип трейта итератора - IntoIterator. Он позволяет рассматривать типы как итеративные, доступные для перебора. Ключевой его компонент - метод into_iter(), который возвращает итератор для определенного типа. Реализация IntoIterator позволяет использовать их в цикле for. Простейший пример:
struct MyCollection {
data: Vec<i32> // условные данные
}
impl MyCollection {
// определяем функцию-конструктор
fn new() -> Self { MyCollection { data: Vec::new() } }
// Добавляем в коллекцию
fn add(&mut self, value: i32) {
self.data.push(value);
}
}
// реализуем трейт IntoIterator
impl IntoIterator for MyCollection {
type Item = i32;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.data.into_iter()
}
}
fn main() {
let mut collection = MyCollection::new();
collection.add(2);
collection.add(4);
collection.add(8);
for item in collection { // перебираем коллекцию
println!("Item: {}", item);
}
}
Здесь мы определяем структуру MyCollection, которая содержит данные в виде вектора типа Vec<i32>. То есть MyCollection будет выступать своего рода контейнером для набора
целых чисел типа i32. Далее реализуем для структуры MyCollection метод new(), который отвечает за создание нового экземпляра MyCollection с пустым вектором. И также реализуем метод
add(), который добавляет числа в вектор.
Затем реализуем трейт IntoIterator для нашей структуры MyCollection. В реализации указываем ассоциированные типы: Item представляет тип элементов в нашей коллекции (в данном случае i32), а
IntoIter указывает, как создать итератор для нашей коллекции. Метод into_iter() определяет, как нашу коллекцию можно использовать в качестве итератора,
позволяя нам перебирать ее содержимое. В функции main создаем экземпляр MyCollection, добавляем в него несколько целых чисел, а затем используем цикл for для перебора коллекции.
Консольный вывод:
Item: 2 Item: 4 Item: 8