Программирование RustВладение и заимствованиеRust-разработчик системного программного обеспечения

При временном чтении через изменяемую ссылку, что происходит с возможностью последующей записи через исходн...

При временном чтении через изменяемую ссылку, что происходит с возможностью последующей записи через исходную ссылку?

Проходите собеседования с ИИ помощником Hintsage

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

Rust создаёт временное повторное заимствование: изменяемая ссылка используется как источник неизменяемой ссылки на ограниченный срок. Пока это чтение активно, запись через исходную &mut запрещена; после последнего использования неизменяемой ссылки исходная изменяемая ссылка снова доступна.

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

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

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

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

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

Поэтому Rust временно приостанавливает использование исходной &mut. Неверное представление, будто изменяемая ссылка просто копируется в &, приводит к ошибкам: копирование неограниченного доступа нарушило бы правило отсутствия одновременного чтения и записи.

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

Выражение вида &*mutable_ref не перемещает значение и не копирует саму изменяемую ссылку. Сначала разыменовывается &mut T, затем на полученное значение создаётся новая, более ограниченная ссылка &T — это и есть reborrow, или повторное заимствование.

На время существования &T исходная &mut T считается недоступной для операций, способных читать или изменять значение. После последнего фактического использования &T компилятор благодаря не лексическим временам жизни может завершить её заимствование ещё до конца блока, и запись через &mut T снова разрешается.

fn inspect(value: &i32) { println!("{value}"); } fn main() { let mut number = 10; let writable = &mut number; inspect(&*writable); *writable += 1; }

Вызов inspect получает временную неизменяемую ссылку на то же значение. writable нельзя использовать для записи внутри вызова и пока неизменяемая ссылка ещё нужна, но после вызова она снова может изменять число.

Повторное заимствование отличается от перемещения &mut T: при reborrow исходная ссылка не становится навсегда недоступной и не меняет владельца значения. Однако нельзя сохранить полученную &T на срок, пересекающийся с последующим использованием &mut для записи.

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

Представим обработчик, который получает &mut Request: сначала нужно передать запрос функции журналирования для чтения, затем изменить его статус.

Возможны три подхода. Клонирование запроса изолирует чтение, но может быть дорогим и потребовать реализации Clone. Передача владения невозможна или нежелательна, поскольку обработчик должен сохранить управление исходным запросом. Использование повторного заимствования не создаёт копию данных и точно ограничивает период чтения.

Поэтому выбирается вызов функции с временным &T, полученным из &mut T. После завершения журналирования исходная &mut Request снова используется для изменения статуса, а проверка компилятора гарантирует отсутствие пересечения чтения и записи.

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

  1. Повторное заимствование копирует значение или ссылку?

    Оно не копирует значение и не делает независимую копию исходной ссылки. Создаётся новый временный доступ к тому же объекту, а исходный &mut на этот период ограничивается правилами заимствования.

  2. Можно ли изменять значение через исходную &mut, пока существует полученная &T?

    Нет, если время использования &T пересекается с изменением. Иначе читатель мог бы получить значение до изменения, во время изменения или после него без гарантии согласованного состояния.

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

  3. Чем повторное заимствование отличается от передачи &mut T в функцию?

    При передаче &mut T функция обычно получает изменяемую ссылку на период вызова; это также может быть оформлено как временное повторное заимствование. После возврата исходная ссылка становится доступной, если функция не вернула из неё заимствование и не сохранила доступ способом, который продлевает срок займа.

    Смысл операции остаётся тем же: владение объектом не меняется, но исходный доступ временно уступает место более узкому заимствованию с контролируемым временем жизни.