В цикле ниже items используется после обхода. Какой механизм Rust делает переменную недоступной после цикла...

В цикле ниже items используется после обхода. Какой механизм Rust делает переменную недоступной после цикла?

fn main() {
    let items = vec![String::from("a"), String::from("b")];

    for item in items {
        println!("{item}");
    }

    println!("{}", items.len());
}
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

for item in items передаёт items во владение итератору через IntoIterator::into_iter. Для Vec<T> такой итератор владеет вектором и перемещает его элементы по одному, поэтому после цикла переменная items больше недоступна.

Если нужно сохранить доступ к вектору, его следует обходить по ссылке: for item in &items или for item in items.iter().

Исторический контекст

Модель владения в Rust решает задачу безопасного управления памятью без сборщика мусора и без ручного освобождения каждого объекта. Компилятор должен точно знать, кто отвечает за ресурс и когда его можно освободить.

Циклы по коллекциям подчиняются тем же правилам, что и обычные вызовы функций: передача значения без ссылки может передать владение. Это позволяет безопасно обрабатывать элементы, не создавая неявных копий и не оставляя неоднозначным владельца коллекции.

Постановка проблемы

В коде items передаётся в цикл по значению. Итератор получает владение всем Vec<String>, а затем выдаёт его элементы циклу; каждый String перемещается в переменную item.

После передачи владения исходное имя нельзя использовать. Попытка вызвать items.len() приводит к ошибке компиляции «use of moved value», потому что Rust не может одновременно считать вектор перемещённым и доступным через прежнюю переменную.

Подробное решение

Конструкция цикла логически основана на вызове IntoIterator::into_iter:

fn main() { let items = vec![String::from("a"), String::from("b")]; for item in &items { println!("{item}"); } println!("{}", items.len()); }

В исходном варианте выражение items используется по значению. Для Vec<T> это означает создание владеющего итератора: сам вектор перемещается в итератор, а его элементы затем передаются в item по одному. Для String это именно перемещение, а не копирование содержимого строки.

При обходе &items в into_iter передаётся ссылка на вектор. Итератор выдаёт &String, не забирая владение ни у вектора, ни у его элементов. Поэтому после завершения цикла items остаётся доступным.

Выбор формы обхода определяет права внутри тела цикла:

  • for item in items — владение элементами перемещается в цикл; исходный вектор после этого недоступен;
  • for item in &items — элементы доступны только для чтения через &String;
  • for item in &mut items — элементы доступны для изменения через &mut String, но вектор временно занят изменяемым заимствованием.

Если коллекция больше не нужна после цикла, потребляющий обход может быть правильным и эффективным: он не требует клонирования элементов. Если коллекция должна использоваться дальше, заимствующий обход обычно предпочтительнее; клонирование является запасным вариантом, но создаёт дополнительные расходы и требует, чтобы элементы реализовывали Clone.

Ситуация из практики

Предположим, обработчик получает Vec<String>, печатает все значения, а затем записывает количество элементов в метрики. Вариант for item in items удобен, если после печати коллекция действительно больше не нужна, но он не позволяет обратиться к items.len() после цикла.

Можно клонировать вектор перед обходом, однако это дублирует все строки и увеличивает потребление памяти. Можно отдельно сохранить длину до цикла, но это подходит только для конкретной метрики и не сохраняет доступ к самой коллекции.

Рациональное решение — for item in &items: обработчик читает строки без копирования, а после цикла использует items.len(). Если обработка должна изменять элементы, применяется for item in &mut items; при этом нельзя одновременно выполнять независимую операцию, требующую другого доступа к вектору, пока активен такой обход.

Что кандидаты часто упускают

  1. Чем отличается for item in items от for item in items.into_iter()?

    Для Vec<T> по смыслу это один и тот же владеющий обход: вызывается IntoIterator::into_iter для самого вектора. В обоих случаях вектор перемещается в итератор, а элементы выдаются по значению. Явная запись полезна для объяснения механизма, но не меняет владение.

  2. Можно ли использовать items после for item in &items, если item ещё находится в области видимости?

    Да. item содержит ссылку, но её заимствование действует только в пределах соответствующей итерации; после завершения цикла эти ссылки уничтожены. Вектор остаётся владельцем элементов и доступен после цикла, если внутри цикла не создано заимствование, выходящее за пределы цикла.

  3. Что произойдёт, если тело цикла сохранит item при обходе по ссылке?

    Сохранить сам &String можно только в структуре или контейнере с подходящим временем жизни: ссылка не может пережить вектор, которому она принадлежит. Если требуется независимое значение после уничтожения вектора, нужно явно скопировать данные, например item.clone(). Это меняет семантику с заимствования на создание нового владельца и несёт стоимость копирования.