Владение, области видимости и перемещения

RustBeginner
Практиковаться сейчас

Введение

Rust использует владение, чтобы определить, какая часть программы отвечает за значение и когда это значение можно удалить. Рассматривайте владение как ответственность и право доступа, а не как постоянную метку, навсегда связанную с одним именем переменной: эта ответственность может переместиться к новой привязке. Благодаря этому Rust предотвращает ряд ошибок памяти без сборщика мусора.

В этой лабораторной работе вы сначала увидите, что небольшое целое число можно скопировать. Затем вы намеренно попросите Rust использовать принадлежащий программе String после его перемещения, прочитаете диагностическое сообщение компилятора и исправите поток данных, используя владение, возвращённое из функции. Каркас кода уже подготовлен, поэтому каждое изменение будет небольшим и целенаправленным.

Наблюдение за областью видимости и копированием целого числа

На этом шаге вы скопируете небольшое целое число во внутреннюю область видимости и убедитесь, что исходная привязка остаётся доступной.

Перейдите в подготовленный проект и откройте исходный файл:

cd /home/labex/project/ownership-demo
nano src/main.rs

Область видимости — это участок программы, в котором привязка действительна. Фигурные скобки создают новую внутреннюю область видимости. Привязка local_count существует только между внутренней парой скобок, а warehouse_count относится к окружающей области видимости функции main.

В строке шага 1 замените только 0 на warehouse_count:

        let local_count = warehouse_count; // Step 1: replace the placeholder.

Целочисленный тип i32 поддерживает поведение Rust Copy. При присваивании значения local_count небольшое значение копируется, поэтому warehouse_count не теряет возможности использовать его.

Сохраните файл с помощью Ctrl+O, нажмите Enter, затем выйдите с помощью Ctrl+X. Запустите программу:

cargo run --quiet

Первые две строки подтверждают, что обе привязки можно использовать:

Local count: 3
Warehouse count: 3

Когда внутренняя область видимости завершится, local_count перестанет быть действительной. Внешняя привязка warehouse_count останется действительной до конца main.

Чтение намеренно вызванной ошибки перемещения

На этом шаге вы намеренно используете String после его перемещения, а затем разберёте диагностическое сообщение компилятора Rust. Ожидаемая ошибка проверки — это результат обучения на данном шаге.

String::from("PX-104") создаёт принадлежащий программе текст. В отличие от небольшого i32, String управляет отдельно выделенным буфером текста, размер которого может изменяться. Rust передаёт ответственность за этот буфер одному владельцу, чтобы удалить его ровно один раз.

Вызов record_parcel(parcel_id) передаёт String по значению. Владение перемещается из привязки parcel_id в функцию. Функция возвращает значение, а вызов сохраняет возвращённого владельца в _returned_id. Начальный символ подчёркивания сообщает Rust, что эта временная привязка пока намеренно не используется.

При перемещении значение не уничтожается. Только старая привязка теряет право его использовать. Путь владения в этой программе выглядит так:

parcel_id in main → parcel_id parameter in record_parcel → _returned_id in main

На каждом этапе остаётся один текущий владелец, ответственный за тот же текст.

Откройте файл:

nano src/main.rs

Найдите строку под комментарием шага 2 и удалите только начальные //, чтобы она стала такой:

    println!("Original after move: {parcel_id}");

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

Запустите проверку. Она должна завершиться ошибкой:

cargo check

Найдите в диагностическом сообщении следующие неизменные фрагменты:

error[E0382]: borrow of moved value: `parcel_id`
...
value moved here
...
value borrowed here after move

Слово borrow появляется потому, что println! нужен только временный доступ к значению, чтобы вывести его. Но такой доступ через parcel_id всё равно невозможен, поскольку владение уже переместилось. На следующем шаге вы используете привязку, которая получила владение обратно из функции.

Продолжение работы с возвращённым владельцем

На этом шаге вы исправите ошибку перемещения, присвоив имя значению, возвращённому из record_parcel, и используя его.

Сигнатура функции fn record_parcel(parcel_id: String) -> String означает, что функция принимает владение String и возвращает String. Её последнее выражение parcel_id передаёт владение обратно вызывающему коду.

Откройте файл:

nano src/main.rs

Внесите два небольших изменения. Сначала переименуйте _returned_id в returned_id в строке вызова функции. Это убирает временное подчёркивание, поскольку теперь вы собираетесь использовать это значение.

Затем замените строку с ошибочным выводом следующей строкой:

    println!("Returned owner: {returned_id}");

Старая привязка parcel_id остаётся недействительной после перемещения. Переименование _returned_id в returned_id не восстанавливает старую привязку — оно даёт обычное имя привязке, которая уже получила владение обратно. Теперь текущим владельцем является returned_id, поэтому использовать нужно именно это имя.

Сохраните файл и выйдите из редактора, затем проверьте исправленную программу:

cargo check

Результат Finished без ошибки E0382 подтверждает, что поток владения корректен. Запустите программу:

cargo run --quiet

Полный вывод должен быть таким:

Local count: 3
Warehouse count: 3
Recorded parcel: PX-104
Returned owner: PX-104

В конце main привязка returned_id покидает область видимости, и Rust удаляет принадлежащий ей String. По-прежнему существует только один владелец, ответственный за эти данные.

Итоги

Вы связали лексическую область видимости со временем жизни значения, увидели копирование i32 без потери возможности использовать исходную привязку, проследили перемещение String в функцию, разобрали ошибку E0382 и безопасно продолжили работу с владением, возвращённым вызывающему коду.