Перейти к содержимому

Итераторы в Rust

Итератор (iterator) — это всё, что умеет выдавать последовательность значений по одному. Ты пользуешься итераторами ещё со страницы Цикл for, даже если не называл их так: за каждым циклом for по коллекции на самом деле стоит итератор.

fn main() {
let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1)
println!("{:?}", iter.next()); // Some(2)
println!("{:?}", iter.next()); // Some(3)
println!("{:?}", iter.next()); // None
}

Вывод:

Some(1)
Some(2)
Some(3)
None
▶ Попробуй сам

.next() возвращает Option: Some(значение) для каждого элемента и None, когда элементы закончились. Цикл for на самом деле просто вызывает .next() снова и снова, пока не получит None.

.map() принимает замыкание и применяет его к каждому элементу, создавая новый итератор:

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);
}

Вывод:

[2, 4, 6, 8, 10]
▶ Попробуй сам

.collect() превращает итератор обратно в конкретную коллекцию (здесь — в Vec<i32>). Без него у тебя был бы просто итератор, который так ничего и не посчитал.

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);
}

Вывод:

[2, 4, 6]
▶ Попробуй сам

Двойное **n выглядит непривычно. .filter() передаёт замыканию ссылку на ссылку (&&i32), поэтому, чтобы сравнить с обычным числом, её нужно разыменовать дважды. Это частая маленькая странность; полное объяснение пока можно не разбирать.

Здесь цепочки методов итератора начинают окупаться: можно объединить несколько шагов в один понятный конвейер:

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers
.iter()
.filter(|n| **n % 2 == 0) // оставляем чётные числа
.map(|n| n * 10) // умножаем каждое на 10
.collect();
println!("{:?}", result);
}

Вывод:

[20, 40, 60]
▶ Попробуй сам

Сравни это с той же логикой, написанной через обычный цикл for и пустой Vec, в который ты добавляешь элементы. Так тоже можно, но цепочка читается почти как «отфильтруй, потом умножь, потом собери» — ровно та последовательность шагов, которую ты описываешь.

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum();
let count = numbers.iter().count();
let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("сумма: {total}, количество: {count}, есть чётные: {has_even}");
}

Вывод:

сумма: 15, количество: 5, есть чётные: true
▶ Попробуй сам

.sum() всё складывает, .count() говорит, сколько элементов, а .any() отвечает на вопрос «подходит ли хотя бы один элемент?». Это лишь несколько методов, которые есть у любого итератора. Их гораздо больше: enumerate, zip, take, rev и другие. На странице Свои итераторы ты напишешь собственный итератор и увидишь, что все эти методы достаются ему даром.

Возьми числа от 1 до 10, оставь только нечётные, возведи каждое в квадрат и сложи всё вместе. Сделай это одной цепочкой итератора, без переменной с mut. Подсказка: соедини .filter(), .map() и .sum() и укажи тип результата, например let total: i32.

Показать решение
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers
.iter()
.filter(|n| **n % 2 == 1) // оставляем 1, 3, 5, 7, 9
.map(|n| n * n) // 1, 9, 25, 49, 81
.sum();
println!("сумма квадратов нечётных: {total}");
}

Вывод:

сумма квадратов нечётных: 165
▶ Попробуй сам

Rust нужен : i32 у total, потому что .sum() умеет складывать в разные числовые типы, и угадать, какой тебе нужен, он не может.

Начни с vec!["the", "quick", "brown", "fox", "jumps"]. Собери новый Vec<String>, в котором только слова длиннее 3 букв, в верхнем регистре, и выведи его через {:?}. Подсказка: .filter(|w| w.len() > 3) оставляет длинные слова, а .to_uppercase() превращает &str в String в верхнем регистре.

Слова здесь английские: .len() считает байты, а буква кириллицы занимает 2 байта, так что проверка > 3 работала бы для неё иначе.

Показать решение
fn main() {
let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words
.iter()
.filter(|w| w.len() > 3)
.map(|w| w.to_uppercase())
.collect();
println!("{:?}", loud);
}

Вывод:

["QUICK", "BROWN", "JUMPS"]
▶ Попробуй сам

Сам words не меняется. .iter() только заимствует его, поэтому после цепочки words по-прежнему можно использовать.

  • Итератор выдаёт значения по одному через .next(), который возвращает Option.
  • Внутри циклов for работают итераторы.
  • .map() преобразует каждый элемент; .filter() оставляет только те, что подходят под условие; .collect() превращает результат обратно в конкретную коллекцию.
  • Цепочка методов итератора обычно понятнее, чем ручной цикл с изменяемым накопителем.

Проверь себя

1. Что возвращает .next() у итератора?

2. Что делает .collect() в конце цепочки вроде .iter().map(...).collect()?

3. Почему в .filter(|n| **n % 2 == 0) n разыменовывается дважды?

4. Что стоит за циклом for, когда он перебирает коллекцию?

5. Что .filter() оставляет в итоговом итераторе?

Результат: 0 / 5