# Глубокое погружение в итераторы в Rust: написание своего типажа Iterator, .iter() vs .into_iter() и нулевая стоимость абстракций

> Подробный разбор итераторов в Rust: ленивые вычисления, различие .iter(), .iter_mut() и .into_iter(), реализация собственного типажа Iterator и нулевая стоимость абстракций.

- **URL:** https://eagle2000.space/rust/rust-iterators-deep-dive/
- **Date:** 2026-08-08

- **Tags:** rust, dev, iterators, zero-cost-abstractions, functional

---
## Введение

В фундаментальном стиле программирования Rust итераторы занимают особое место. Они сочетают декларативный функциональный стиль с высочайшей производительностью системного языка. 

В отличие от многих динамических языков, где вызовы `.map()` или `.filter()` приводят к созданию промежуточных массивов и лишним нагрузкам на сборщик мусора, итераторы в Rust работают по принципу **Нулевой стоимости абстракций (Zero-Cost Abstractions)**.

В этой статье мы глубоко погрузимся в устройство итераторов: разберем три вида заимствования при итерации, поймем механику ленивых вычислений (Lazy Evaluation), напишем собственный генератор последовательности и посмотрим, как компилятор собирает функциональные цепочки в оптимальный машинный код.

---

## 1. Три вкуса итерации: .iter(), .iter_mut() и .into_iter()

В Rust управление памятью строго связано с системой заимствований. Поэтому практически любой контейнер (вектор, массив, хеш-таблица) предоставляет три разных способа итерации по своим элементам:

### 1. Заимствование по ссылке: `.iter()`
Метод `.iter()` возвращает итератор, выдающий неизменяемые ссылки `&T`.
* **Что происходит:** Коллекция остается полностью сохранной и доступной для дальнейшего использования.
* **Тип элемента:** `&T`.

### 2. Мутабельное заимствование: `.iter_mut()`
Метод `.iter_mut()` возвращает итератор по мутабельным ссылкам `&mut T`.
* **Что происходит:** Позволяет изменять элементы коллекции «на месте» во время обхода.
* **Тип элемента:** `&mut T`.

### 3. Поглощение и перемещение: `.into_iter()`
Типаж `IntoIterator` и метод `.into_iter()` поглощают исходную коллекцию.
* **Что происходит:** Коллекция передает владение своими элементами `T` и после завершения цикла прекращает свое существование (перемещается).
* **Тип элемента:** `T`.

```rust
let names = vec!["Анна".to_string(), "Борис".to_string()];

// Перемещаем владение элементами из вектора в цикл
for name in names.into_iter() {
    println!("Привет, {name}");
}

// names больше нельзя использовать — вектор поглощен!
```

---

## 2. Ленивые вычисления (Lazy Evaluation)

Одно из главных свойств итераторов в Rust — они **абсолютно ленивы**.

Когда вы вызываете комбинаторы вроде `.map()`, `.filter()`, `.take()` или `.zip()`, Rust не выполняет никаких вычислений и не совершает ни одной аллокации. Вместо этого он конструирует новую структуру данных, которая бережливо хранит инструкции по обработке.

```rust
let numbers = vec![1, 2, 3, 4, 5];

// Данный код НЕ выполняет умножение! Он лишь создает обертку Map:
let doubled = numbers.iter().map(|x| {
    println!("Вычисляем для {x}");
    x * 2
});

// Ни одна строчка "Вычисляем для..." еще не напечатана!
```

Вычисление начинается только тогда, когда вы вызываете **терминальный метод (consuming adaptor)**:
* `.collect()` — собирает результаты в новую коллекцию (`Vec`, `HashSet`, `BTreeMap`).
* `.fold()` / `.reduce()` — аккумулирует значения в одно.
* `.for_each()` — выполняет замыкание для каждого элемента.
* `.sum()` / `.count()` / `.find()` / `.next()` — запрашивают вычисление значений.

---

## 3. Написание собственного типажа Iterator

Чтобы превратить любую вашу структуру данных в полноценный итератор, достаточно реализовать стандартный типаж **`std::iter::Iterator`**.

Удивительно, но этот типаж требует реализации **всего одного метода** — `next()`:

```rust
pub trait Iterator {
    type Item; // Ассоциированный тип элемента

    fn next(&mut self) -> Option<Self::Item>;
}
```

Как только вы написали `next()`, ваша структура **бесплатно и автоматически** получает более 70 встроенных методов-адаптеров (`map`, `filter`, `take`, `zip`, `enumerate`, `collect` и многие другие)!

### Пример: Итератор степеней двойки

```rust
struct PowerOfTwo {
    curr: u64,
}

impl PowerOfTwo {
    fn new() -> Self {
        PowerOfTwo { curr: 1 }
    }
}

impl Iterator for PowerOfTwo {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let res = self.curr;
        self.curr = self.curr.checked_mul(2)?; // Прекращаем итерацию при переполнении
        Some(res)
    }
}

fn main() {
    // Используем стандартные адаптеры take и collect для нашего итератора!
    let powers: Vec<u64> = PowerOfTwo::new().take(5).collect();
    println!("{:?}", powers); // [1, 2, 4, 8, 16]
}
```

---

## 4. Нулевая стоимость абстракций (Zero-Cost Abstractions)

Разработчики из других языков часто опасаются, что длинные цепочки функциональных методов вида:

```rust
let sum: i32 = vec.iter()
    .filter(|&&x| x > 0)
    .map(|&x| x * 2)
    .sum();
```

будут работать медленнее, чем обычный традиционный цикл `for i in 0..len`.

В Rust это заблуждение. За счет механизмов **мономорфизации (Monomorphization)** и агрессивного **инлайнинга (Inlining)** оптимизатор LLVM полностью разворачивает всю цепочку комбинаторов в один компактный машинный цикл ассемблера.

Более того: итераторы в Rust часто работают **быстрее**, чем циклы с индексацией по массиву `arr[i]`, потому что при итерации компилятор статически доказывает безопасность и полностью **удаляет проверки выхода за границы массива (Bounds Check Elimination)**!

Давайте проверим эти концепции на практических интерактивных слайдах:


### 💻 Устройство и работа итераторов в Rust


#### Шаг 1

```rust
fn main() {
    let mut numbers = vec![10, 20, 30];

    println!("--- 1. .iter() — заимствование по &T ---");
    for num in numbers.iter() {
        println!("Элемент по ссылке: {num}");
    }

    println!("\n--- 2. .iter_mut() — изменение по &mut T ---");
    for num in numbers.iter_mut() {
        *num += 5;
    }
    println!("Модифицированный вектор: {:?}", numbers);

    println!("\n--- 3. .into_iter() — перемещение владения T ---");
    for num in numbers.into_iter() {
        println!("Поглощенное значение: {num}");
    }
    // numbers больше недоступен — владение перемещено в итератор!
}
```

**Три вкуса заимствования при итерации**
- `.iter()` выдает неизменяемые ссылки `&T`. Вектор остается доступен после цикла.
- `.iter_mut()` выдает мутабельные ссылки `&mut T` для изменения элементов на месте.
- `.into_iter()` поглощает коллекцию и выдает владение `T`. После вызова коллекция перемещается.


#### Шаг 2

```rust
// Пользовательский итератор последовательности Фибоначчи
struct Fibonacci {
    curr: u64,
    next: u64,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci { curr: 0, next: 1 }
    }
}

// Реализуем ключевой типаж Iterator
impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let current = self.curr;
        self.curr = self.next;
        self.next = current + self.next;

        Some(current)
    }
}

fn main() {
    println!("--- Первые 8 чисел Фибоначчи из нашего итератора ---");
    let fib_seq: Vec<u64> = Fibonacci::new().take(8).collect();
    println!("Результат: {:?}", fib_seq);
}
```

**Реализация собственного типажа Iterator**
- Для создания итератора достаточно задать ассоциированный тип `type Item` и метод `next(&mut self) -> Option<Item>`.
- После реализации `next()`, объект автоматически бесплатно получает десятки методов-адаптеров (`take`, `map`, `filter`, `collect` и т.д.).


#### Шаг 3

```rust
fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    println!("--- Цепочка адаптеров (Ленивые вычисления) ---");
    let iter = numbers.iter()
        .filter(|&&x| x % 2 == 0) // Оставляем четные
        .map(|&x| x * x);          // Возводим в квадрат

    // До вызова терминального метода (fold / collect) ни один элемент НЕ вычисляется!

    let sum_of_squares: i32 = iter.sum(); // Терминальный вызов производит вычисление
    println!("Сумма квадратов четных чисел: {sum_of_squares}");
}
```

**Комбинаторы и Ленивые вычисления**
- Адаптеры вроде `.map()` и `.filter()` являются ленивыми (lazy). Они создают новые итераторы, но не выполняют вычислений.
- Вычисления запускаются только при вызове терминальных методов (`collect`, `sum`, `fold`, `for_each`).
- Компилятор Rust объединяет цепочки адаптеров в один машинный цикл с нулевыми накладными расходами.


---

## Проверь свои знания!

Пройдите короткий интерактивный тест по теме итераторов:


### 📝 Итераторы в Rust


**1. В чем разница между вызовами `.iter()` и `.into_iter()` для векторного контейнера `Vec<T>`?**

- [ ] .iter() заимствует элементы по ссылке &T, а .into_iter() поглощает вектор и передает владение значениями T
- [ ] .into_iter() работает исключительно со статистическими константами
- [ ] .iter() удаляет элементы из вектора в процессе итерации

**2. Что произойдет, если написать `numbers.iter().map(|x| x * 2)`, но не вызвать терминальный метод (`collect`, `sum`, `for_each`)?**

- [ ] Никаких вычислений не произойдет — итераторы в Rust ленивые (lazy)
- [ ] Программа вызовет панику во время выполнения
- [ ] Элементы мгновенно умножатся в исходном векторе

**3. Какой единственный метод обязателен для реализации типажа `Iterator`?**

- [ ] fn next(&mut self) -> Option<Self::Item>
- [ ] fn collect(self) -> Vec<Self::Item>
- [ ] fn count(self) -> usize

**4. Что обеспечивает 'Нулевую стоимость абстракций' (Zero-Cost Abstractions) при работе с итераторами в Rust?**

- [ ] Оптимизации LLVM и мономорфизация объединяют цепочки map/filter в плоский машинный цикл без промежуточных аллокаций
- [ ] Итераторы не используют память в куче по определению
- [ ] Компилятор пропускает проверку выходя за границы массива

