Как итерироваться по Vec<T> с получением индекса элемента в Rust?
Использование .iter().enumerate() — идиоматичный способ
Да, вы абсолютно правы: в Rust есть прямой аналог Python enumerate. Это метод .enumerate(), который вызывается на итераторе. Он возвращает кортеж (index, value) для каждого элемента.
Вот как это выглядит для вашего случая:
let items = vec!["яблоко", "банан", "вишня"];
for (index, value) in items.iter().enumerate() {
println!("{}: {}", index, value);
}
// Вывод:
// 0: яблоко
// 1: банан
// 2: вишня
Разберём цепочку вызовов по шагам:
items.iter()— создаёт итератор, который заимствует элементы вектора (не перемещая их). Тип элементов —&str(ссылки на строки)..enumerate()— оборачивает итератор, добавляя к каждому элементу его порядковый номер (начиная с 0). Возвращает кортеж(usize, &str).for (index, value) in ...— деструктурирует кортеж на две переменные.
Почему это лучше, чем доступ по индексу?
Ваш первый вариант с items[i] действительно имеет несколько недостатков:
- Безопасность:
items[i]вызывает панику при выходе за границы. Хотя в простом цикле0..items.len()этого не произойдёт, при модификации кода легко ошибиться..enumerate()гарантирует, что вы получите ровно столько итераций, сколько элементов в векторе. - Производительность:
items[i]каждый раз проверяет границы (bounds check). Итератор сenumerateобычно оптимизируется компилятором лучше, так как он знает точные границы. - Идиоматичность: В Rust принято использовать итераторы. Код становится более декларативным и читаемым.
Варианты для разных ситуаций
Если вам нужно владение элементами (а не ссылки):
let items = vec!["яблоко".to_string(), "банан".to_string()];
for (index, value) in items.into_iter().enumerate() {
// value имеет тип String (владение)
println!("{}: {}", index, value);
}
// После этой итерации items больше недоступен
Если нужна изменяемая ссылка:
let mut items = vec![10, 20, 30];
for (index, value) in items.iter_mut().enumerate() {
*value += index; // умножаем каждый элемент на его индекс
}
// items теперь [10, 21, 32]
Если нужно начать не с нуля:
for (index, value) in items.iter().enumerate().skip(1) {
// пропустит первый элемент (индекс 0)
}
Если нужен обратный порядок:
for (index, value) in items.iter().enumerate().rev() {
// пойдёт от последнего к первому, индексы тоже будут в обратном порядке
}
Важное замечание про .enumerate() и .zip()
.enumerate() — это частный случай .zip() с бесконечным диапазоном 0... Вы можете написать эквивалентный код вручную:
for (index, value) in (0..).zip(items.iter()) {
println!("{}: {}", index, value);
}
Но .enumerate() короче и понятнее. Используйте его.
Итог: всегда используйте iter().enumerate() (или iter_mut().enumerate() / into_iter().enumerate()) для итерации по вектору с индексом. Это безопасно, эффективно и соответствует идиомам Rust.
Альтернативный подход: доступ по индексу с проверкой границ
Хотя .iter().enumerate() — предпочтительный способ, иногда вам может понадобиться доступ по индексу, например, когда нужно обращаться к соседним элементам. В таких случаях используйте .get() вместо квадратных скобок:
let items = vec!["a", "b", "c"];
for i in 0..items.len() {
if let Some(value) = items.get(i) {
println!("{}: {}", i, value);
}
}
Метод .get() возвращает Option<&T>. Если индекс выходит за границы, вы получите None вместо паники. Это безопасно, но менее удобно, чем enumerate.
Когда может пригодиться доступ по индексу?
- Обращение к элементам с произвольным шагом (например, каждый второй).
- Одновременный доступ к нескольким элементам (например,
items[i]иitems[i+1]). - Работа с многомерными структурами (вектор векторов).
В этих случаях комбинируйте .enumerate() с .get() или используйте windows() / chunks() для работы со скользящими окнами. Но для базовой итерации с индексом — enumerate всегда лучший выбор.
Ответов пока нет.
Войдите чтобы оставить ответ