Avatar ·

В чем разница между iter() и into_iter() в Rust?

Привет! Я только начал изучать Rust и пытаюсь разобраться с итераторами. Прочитал несколько статей, но никак не могу понять разницу между методами `iter()` и `into_iter()`. Я написал такой код: ```rust let numbers = vec![1, 2, 3, 4, 5]; // Попытка 1: iter() for num in numbers.iter() { println!("{}", num); } println!("После iter(): {:?}", numbers); // Работает // Попытка 2: into_iter() for num in numbers.into_iter() { println!("{}", num); } println!("После into_iter(): {:?}", numbers); // Ошибка! ``` Первая часть кода работает нормально, я могу использовать `numbers` после цикла. Но вторая часть выдает ошибку компиляции: `value used here after move`. Я читал, что `into_iter()` потребляет коллекцию, но не понимаю, что это значит на практике. Почему я не могу использовать вектор после `into_iter()`? И в каких ситуациях нужно применять каждый из этих методов? Также я слышал про метод `iter_mut()`, но пока не разобрался с ним. Буду благодарен за подробное объяснение с примерами! <<>>

Ключевое различие: владение и заимствование

Главное отличие между iter() и into_iter() заключается в том, как они работают с владением (ownership) данными. Это фундаментальная концепция Rust, которая обеспечивает безопасность памяти.

Метод iter() — заимствование (Borrowing)

iter() создает итератор, который заимствует элементы коллекции. Он возвращает ссылки &T на каждый элемент. Коллекция остается владельцем данных, и вы можете продолжать использовать её после итерации.

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

// iter() возвращает &i32 — неизменяемые ссылки
for num in numbers.iter() {
    println!("{}", num); // num: &i32
}

// numbers все еще доступен
println!("Вектор не тронут: {:?}", numbers); // ✅ Работает

Метод into_iter() — потребление (Consumption)

into_iter() потребляет коллекцию, забирая владение её элементами. Он возвращает сами значения T, а не ссылки. После вызова into_iter() оригинальная коллекция больше недоступна.

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

// into_iter() возвращает i32 — владение значениями
for num in numbers.into_iter() {
    println!("{}", num); // num: i32 (не ссылка!)
}

// numbers перемещен (moved) — больше не доступен
// println!("{:?}", numbers); // ❌ Ошибка компиляции: borrow of moved value

Когда использовать каждый метод?

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

Практический пример: трансформация данных

let strings = vec!["hello".to_string(), "world".to_string()];

// iter() — только читаем
let lengths: Vec<usize> = strings.iter().map(|s| s.len()).collect();
println!("Длины: {:?}", lengths); // ✅ strings все еще доступен

// into_iter() — забираем владение строками
let uppercased: Vec<String> = strings.into_iter().map(|s| s.to_uppercase()).collect();
// println!("{:?}", strings); // ❌ strings больше не существует
println!("Верхний регистр: {:?}", uppercased);

Метод iter_mut() — изменяемое заимствование

Существует третий вариант — iter_mut(). Он возвращает изменяемые ссылки &mut T, позволяя модифицировать элементы на месте:

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

for num in numbers.iter_mut() {
    *num *= 2; // Удваиваем каждое число
}

println!("Измененный вектор: {:?}", numbers); // [2, 4, 6, 8, 10]

Сводная таблица

Метод Тип элемента Влияние на коллекцию
iter() &T (неизменяемая ссылка) Не изменяет, коллекция остается
iter_mut() &mut T (изменяемая ссылка) Может изменять, коллекция остается
into_iter() T (владение) Потребляет коллекцию

Выбор метода зависит от вашей задачи: нужно ли вам просто прочитать данные, изменить их или полностью переместить. Начинающим рекомендуется по умолчанию использовать iter(), если нет особой причины делать иначе.

<<>>

Дополнение: неявные вызовы into_iter() и производительность

Важно знать, что Rust автоматически вызывает into_iter() в некоторых ситуациях, что может сбивать с толку новичков.

Неявный вызов в for-циклах

Когда вы пишете:

let v = vec![1, 2, 3];
for x in v {  // Неявно вызывает into_iter()
    println!("{}", x);
}
// v недоступен после цикла!

Это эквивалентно for x in v.into_iter(). Если вы хотите сохранить коллекцию, используйте for x in &v (что вызывает iter()) или for x in v.iter() явно.

Производительность

С точки зрения производительности, iter() и into_iter() обычно компилируются в одинаково эффективный код. Разница в семантике, а не в скорости. Однако:

  • into_iter() может быть быстрее при работе с типами, которые не реализуют Copy (например, String), так как избегает копирования данных.
  • iter() предпочтительнее для примитивных типов (i32, f64), так как они копируются дешево.

Совет для новичков

Если вы не уверены, какой метод использовать, начните с iter(). Компилятор Rust достаточно умен, чтобы подсказать, если нужно другое владение. И помните: ошибка "use of moved value" — это не баг, а фича, защищающая вас от проблем с памятью!