Avatar ·

Как итерироваться по Vec<T> с получением индекса элемента в Rust?

Всем привет! Я начал изучать Rust недавно, и у меня возникла проблема с обходом вектора с доступом к индексу элемента. В других языках, например в Python или JavaScript, я привык писать что-то вроде `for i, value in enumerate(list)` или `for (let i = 0; i < arr.length; i++)`. А в Rust я знаю, что можно просто использовать `for value in vec`, но тогда я теряю индекс. Мне нужно вывести на экран все элементы вектора вместе с их позицией, например: ```rust let items = vec!["яблоко", "банан", "вишня"]; // Хочу получить: "0: яблоко", "1: банан", "2: вишня" ``` Пробовал сделать так: ```rust for i in 0..items.len() { println!("{}: {}", i, items[i]); } ``` Это работает, но я слышал, что доступ по индексу через квадратные скобки может вызвать панику, если индекс выходит за границы. Хотя здесь я контролирую длину, это выглядит не очень идиоматично для Rust. К тому же, если вектор будет изменяться во время итерации, могут быть проблемы. Как правильно и безопасно итерироваться по вектору с индексом? Есть ли какой-то встроенный метод, похожий на `enumerate` в Python? Или нужно использовать что-то вроде `.iter().enumerate()`? Буду благодарен за подробное объяснение с примерами. <<>>

Использование .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] действительно имеет несколько недостатков:

  1. Безопасность: items[i] вызывает панику при выходе за границы. Хотя в простом цикле 0..items.len() этого не произойдёт, при модификации кода легко ошибиться. .enumerate() гарантирует, что вы получите ровно столько итераций, сколько элементов в векторе.
  2. Производительность: items[i] каждый раз проверяет границы (bounds check). Итератор с enumerate обычно оптимизируется компилятором лучше, так как он знает точные границы.
  3. Идиоматичность: В 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 всегда лучший выбор.