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() { ... }
Запом