iter vs into_iter в Rust: что скрывается под капотом?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

iter vs into_iter: битва титанов итерации в Rust

Если вы когда-нибудь писали на Rust цикл по вектору, вы наверняка сталкивались с тремя братьями-близнецами: iter(), into_iter() и iter_mut(). На первый взгляд, все они делают одно и то же — позволяют пройтись по элементам. Но разница между ними глубже, чем кажется, и кроется она в самом сердце Rust — системе владения.

Представьте, что вы — бариста, а ваша коллекция — это кофейные зерна. iter() даёт вам посмотреть на зерна, понюхать их, но не трогать. into_iter() — это когда вы берёте зерна, мелете их и делаете эспрессо, после чего зерна исчезают. А iter_mut() — вы можете не только смотреть, но и перемалывать зерна прямо в мешке, меняя их форму.

Сегодня мы разберём iter vs into_iter так, как это объясняют на Stack Overflow, но с живыми примерами и глубоким погружением под капот. Поехали!

Что такое итератор в Rust?

Итератор — это объект, который знает, как последовательно получать элементы из коллекции. В Rust итераторы ленивы: они ничего не делают, пока вы не вызовете next(). Но главное — каждый метод итерации по-разному относится к владению данными.

Вот три главных подхода:

  • iter() — заимствует элементы (не забирает владение)
  • into_iter() — потребляет коллекцию (забирает владение)
  • iter_mut() — заимствует с возможностью изменения

Давайте посмотрим на код. Допустим, у нас есть вектор имён:

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

Пример 1: iter() — только посмотреть

fn main() {
    let names = vec!["Анна".to_string(), "Борис".to_string(), "Виктор".to_string()];
    
    // iter() возвращает &String (ссылку)
    for name in names.iter() {
        println!("Привет, {}!", name);
    }
    
    // names всё ещё доступен!
    println!("Вектор жив: {:?}", names);
}

Обратите внимание: после цикла мы спокойно используем names. Почему? Потому что iter() вернул ссылку на каждый элемент. Владение осталось у вектора. Это безопасно и предсказуемо.

Пример 2: into_iter() — сжечь мосты

fn main() {
    let names = vec!["Анна".to_string(), "Борис".to_string(), "Виктор".to_string()];
    
    // into_iter() возвращает String (владение перемещается)
    for name in names.into_iter() {
        println!("Прощай, {}!", name);
    }
    
    // Ошибка! names больше не существует
    // println!("Вектор жив: {:?}", names); // ❌ borrow of moved value
}

Вот оно, ключевое отличие. into_iter() потребляет коллекцию. Каждый элемент перемещается в цикл, и исходный вектор становится недействительным. Если вам нужно сохранить коллекцию для дальнейшего использования — into_iter() не ваш выбор.

Пример 3: iter_mut() — изменить, не разрушая

fn main() {
    let mut names = vec!["Анна".to_string(), "Борис".to_string(), "Виктор".to_string()];
    
    // iter_mut() возвращает &mut String (мутабельная ссылка)
    for name in names.iter_mut() {
        name.push_str(" (гость)");
    }
    
    println!("Изменённый вектор: {:?}", names);
    // Вывод: ["Анна (гость)", "Борис (гость)", "Виктор (гость)"]
}

Тут мы меняем элементы на месте, но не забираем владение. Идеально для трансформаций.

Под капотом: что возвращает каждый метод?

Чтобы понять iter vs into_iter по-настоящему, заглянем в стандартную библиотеку. Для вектора Vec<T>:

  • iter() возвращает std::slice::Iter<T> — реализует Iterator<Item = &T>
  • into_iter() возвращает std::vec::IntoIter<T> — реализует Iterator<Item = T>
  • iter_mut() возвращает std::slice::IterMut<T> — реализует Iterator<Item = &mut T>

Обратите внимание на Item:

  • iter() → &T (ссылку)
  • into_iter() → T (значение, владение перемещается)
  • iter_mut() → &mut T (мутабельную ссылку)

Почему это важно? Практический пример

Представьте, что вы пишете функцию, которая фильтрует числа и возвращает только чётные. Если вы используете into_iter(), исходный вектор исчезнет:

fn filter_even(numbers: Vec<i32>) -> Vec<i32> {
    numbers.into_iter().filter(|&x| x % 2 == 0).collect()
}

fn main() {
    let nums = vec![1, 2, 3, 4, 5];
    let evens = filter_even(nums);
    // nums больше не доступен!
    println!("{:?}", evens); // [2, 4]
}

А если вы хотите сохранить оригинал — используйте iter():

fn filter_even_ref(numbers: &[i32]) -> Vec<i32> {
    numbers.iter().filter(|&&x| x % 2 == 0).copied().collect()
}

fn main() {
    let nums = vec![1, 2, 3, 4, 5];
    let evens = filter_even_ref(&nums);
    println!("Оригинал: {:?}", nums); // всё жив!
    println!("Чётные: {:?}", evens); // [2, 4]
}

Тонкий момент: for x in vec vs vec.into_iter()

Многие новички пишут for x in vec и удивляются, что вектор исчезает. На самом деле, синтаксический сахар for x in vec автоматически вызывает into_iter(). Да, это так! Компилятор преобразует:

for x in vec { ... }
// Эквивалентно:
for x in vec.into_iter() { ... }

Но если вы напишете for x in &vec, то это вызовет iter():

for x in &vec { ... }
// Эквивалентно:
for x in vec.iter() { ... }

Запом

Блоги

Рекомендаций по книгам