Разберите последствия: что делает Rust с прежним значением владеющей переменной при присваивании ей нового значения?
При присваивании нового значения уже инициализированной переменной Rust сначала уничтожает прежнее значение, если оно владеет ресурсом и реализует Drop, а затем записывает новое. Это предотвращает утечку ресурса: старый String, файл или другой ресурс не остаётся без владельца.
Само присваивание не клонирует новое значение автоматически. Правое выражение обычно перемещается в переменную, а для типов Copy значение копируется побитово.
Владение и детерминированное уничтожение появились как альтернатива сборке мусора и ручному управлению памятью. Такой подход позволяет освобождать ресурсы в заранее определённый момент, сохраняя контроль над производительностью.
Одновременно компилятор должен исключать двойное освобождение, использование после освобождения и потерю ресурса при замене значения. Поэтому присваивание владеющей переменной рассматривается не как простая запись байтов, а как операция над текущим владельцем.
Представим переменную, которая владеет буфером или файловым дескриптором. Если просто заменить ссылку на этот ресурс новым значением, не обработав старое, прежний ресурс можно потерять или освободить слишком поздно.
Обратная ошибка также опасна: если старое значение освободить дважды либо продолжить использовать после замены, возникнут нарушения безопасной памяти. Rust должен корректно завершить владение старым значением и передать владение новым.
Важно отличать присваивание от затенения. При затенении создаётся новое связывание, а старое обычно остаётся действующим до конца своей области видимости; при присваивании изменяется уже существующее место хранения.
Для значения с Drop присваивание в уже инициализированное место логически состоит из двух этапов: старое значение покидает это место и уничтожается, после чего новое значение занимает его. Например:
При выполнении сначала будет уничтожен old, затем напечатается done, а new будет уничтожен при выходе item из области видимости. Компилятор не позволяет оставить прежнее значение недоступным без корректного завершения его жизненного цикла.
Правое выражение не клонируется автоматически. Если тип не реализует Copy, его значение перемещается; если реализует Copy, создаётся копия. Для типов с ресурсами это означает, что новое значение получает владение своим ресурсом, а старое прекращает существование.
Если старое значение нужно не уничтожить, а получить наружу, используют операции вроде std::mem::replace или std::mem::take. Они временно заменяют значение и возвращают прежнее владельцу, поэтому ресурс не теряется.
Значимое ограничение связано с заимствованиями: нельзя присвоить новое значение через место, которое в этот момент занято несовместимым активным заимствованием. Иначе ссылка могла бы продолжить указывать на данные, которые уже уничтожены или заменены.
В конфигурационном сервисе объект содержит большое текущее состояние и периодически получает обновлённую конфигурацию. Простое присваивание новой конфигурации удобно и не требует ручного освобождения старой: её ресурсы освобождаются в момент замены.
Вариант с клонированием проще концептуально, но может временно хранить две полные копии и заметно увеличить потребление памяти. Ручное освобождение старого ресурса опаснее: легко получить двойное освобождение или забыть обработать один из вложенных ресурсов.
Если старую конфигурацию нужно передать журналированию, откату или другому компоненту, прямого присваивания недостаточно: оно уничтожит старое значение. В этом случае выбирают std::mem::replace или std::mem::take, чтобы одновременно установить новое состояние и сохранить старое как возвращаемое значение.
На практике обычно выбирают прямое присваивание, когда старое состояние больше не нужно, и операцию замены с возвратом, когда требуется откат или аудит. Это даёт детерминированное освобождение без ручного управления памятью.
Перемещение в другую переменную оставляет исходное связывание в состоянии, в котором его нельзя использовать как прежнее значение. При присваивании новое значение записывается в уже существующее место, поэтому прежнее значение сначала завершается, а само связывание продолжает существовать и теперь владеет новым значением.
Нет, автоматического клонирования нет. Для некопируемого типа происходит перемещение, а для типа Copy — неявное копирование значения.
Например, присваивание нового String передаёт владение буфером новой переменной; содержимое строки не дублируется. Если нужна независимая копия, её необходимо запросить явно через clone, принимая связанные с этим затраты.
Используют std::mem::replace, передавая новое значение, или std::mem::take, если для типа существует значение по умолчанию. Эти операции атомарно с точки зрения безопасного API заменяют содержимое и возвращают прежнее значение владельцу.
Это отличается от последовательного извлечения старого значения с последующей записью нового: Rust не разрешает оставлять обычную переменную временно неинициализированной. replace и take решают задачу безопасно и корректно обрабатывают владение ресурсами.