Итераторы в 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 — преобразуем каждый элемент
Заголовок раздела «map — преобразуем каждый элемент».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>). Без него у тебя был бы просто итератор, который так ничего и не посчитал.
filter — оставляем только часть элементов
Заголовок раздела «filter — оставляем только часть элементов»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), поэтому, чтобы сравнить с обычным числом, её нужно разыменовать дважды. Это частая маленькая странность; полное объяснение пока можно не разбирать.
Цепочка из map и filter
Заголовок раздела «Цепочка из map и filter»Здесь цепочки методов итератора начинают окупаться: можно объединить несколько шагов в один понятный конвейер:
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. Сумма квадратов нечётных
Заголовок раздела «1. Сумма квадратов нечётных»Возьми числа от 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}");}Вывод:
сумма квадратов нечётных: 165Rust нужен : i32 у total, потому что .sum() умеет складывать в разные числовые типы, и угадать, какой тебе нужен, он не может.
2. Длинные слова — капслоком
Заголовок раздела «2. Длинные слова — капслоком»Начни с 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
Что почитать
Заголовок раздела «Что почитать»- Обработка последовательности элементов с помощью итераторов — книга «Язык программирования Rust» (перевод на русский): официальное введение в
next,map,filterиcollect, с дополнительными примерами. - Сравнение производительности: циклы и итераторы — книга «Язык программирования Rust» (перевод на русский): почему цепочки итераторов так же быстры, как цикл, написанный вручную.
- Трейт
Iterator— документация стандартной библиотеки (на английском): полный список методов итератора, у каждого — небольшой пример. Его приятно просто полистать. - Итераторы — Rust на примерах (перевод на русский): короткий запускаемый пример, в котором строится итератор Фибоначчи.