В чем разница между iter() и into_iter() в Rust?
Ключевое различие: владение и заимствование
Главное отличие между 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" — это не баг, а фича, защищающая вас от проблем с памятью!