В какой момент Rust освобождает ресурс, принадлежащий локальной переменной, если её владелец выходит из области видимости?
Когда владелец выходит из своей области видимости, Rust автоматически уничтожает принадлежащее ему значение: вызывается его механизм Drop, после чего освобождаются связанные ресурсы. Локальные переменные обычно уничтожаются в обратном порядке объявления; освободить значение раньше можно явным вызовом std::mem::drop.
Этот подход решает задачу управления ресурсами без сборщика мусора. Владение связывает время жизни ресурса с временем жизни значения, поэтому освобождение памяти, файловых дескрипторов и блокировок происходит детерминированно и не требует ручного вызова специальной функции очистки.
В отличие от схемы с трассирующим сборщиком мусора, Rust не должен периодически искать недостижимые объекты. Цена этого подхода — необходимость соблюдать правила владения и заранее определять, кто отвечает за ресурс.
Если ресурс удерживается дольше необходимого, он может блокировать файл, удерживать мьютекс или исчерпать ограниченный системный ресурс. Если освободить его слишком рано, последующее обращение к владельцу должно быть запрещено компилятором, иначе возникли бы ошибки использования после освобождения.
Особенно важен случай, когда операция после работы с ресурсом зависит от немедленного закрытия ресурса. Например, на некоторых платформах переименование файла невозможно, пока открытый файловый дескриптор продолжает существовать.
Для каждого значения компилятор определяет область уничтожения. При выходе из неё запускается так называемый drop glue: для значения с Drop вызывается drop, затем рекурсивно уничтожаются его поля. Для типов без пользовательского Drop выполняется необходимое уничтожение вложенных значений.
Перемещение не уничтожает значение в момент передачи: оно переносит право уничтожения новому владельцу. Старое имя после перемещения использовать нельзя, а новый владелец уничтожит значение при завершении своей области видимости.
Явный std::mem::drop — это обычная функция, которая принимает значение по значению. Передача в неё перемещает значение и тем самым завершает его жизнь в текущей точке:
После drop(file) переменная file больше недоступна, зато файловый дескриптор закрыт до rename. Ссылки не становятся владельцами и сами ресурс не уничтожают; они лишь временно предоставляют доступ к значению.
Порядок уничтожения локальных переменных обычно обратен порядку их объявления. Однако точные моменты уничтожения временных значений зависят от правил областей уничтожения выражений, поэтому на важное освобождение ресурса лучше не полагаться на неочевидную форму выражения — следует использовать отдельный блок или явный drop.
Детерминированное уничтожение удобно для ресурсов, но не решает все задачи автоматически. Например, циклическая структура из Rc может оставаться достижимой через саму себя; для разрыва таких циклов используют Weak.
Сервис записывает временный файл, а затем атомарно публикует его переименованием. Вариант с ожиданием конца функции прост, но удерживает файловый дескриптор дольше необходимого и может не работать на платформах с запретом переименования открытого файла.
Вариант с ручным вызовом низкоуровневого закрытия потенциально опасен: можно забыть закрыть дескриптор или закрыть его дважды. Выбранное решение — ограничить владельца отдельным блоком либо вызвать drop сразу после записи. Rust проверяет отсутствие дальнейшего использования переменной, а закрытие выполняется ровно один раз.
В результате ресурс освобождается до публикации файла, код остаётся безопасным при раннем возврате или панике, а время удержания системного ресурса явно видно из программы.
Drop память объекта?Нет, не обязательно. Метод Drop::drop отвечает за пользовательскую очистку ресурса, например закрытие файла или освобождение внешнего дескриптора. После этого компилятор выполняет дальнейшее уничтожение значения, а для Box<T> — также освобождает выделенную область памяти через соответствующий механизм размещения.
Поэтому Drop нельзя вызывать напрямую для ручного повторного освобождения: безопасный интерфейс обычно использует перемещение в std::mem::drop, которое не допускает двойного уничтожения.
При панике с раскруткой стека Rust уничтожает активные локальные значения, как при обычном выходе из соответствующих областей. Это позволяет закрывать файлы и освобождать блокировки даже на пути ошибки.
При стратегии panic = "abort" процесс завершается немедленно, поэтому рассчитывать на выполнение деструкторов нельзя. Внешние ресурсы, требующие обязательного протокола завершения, должны обрабатываться с учётом выбранной стратегии паники.
Счётчик сильных ссылок Rc уменьшается при уничтожении каждого Rc, но цикл сохраняет ненулевое число сильных ссылок. Ни один объект в таком цикле не становится последним владельцем, поэтому его Drop не вызывается, даже если из программы больше нет внешнего доступа к циклу.
Для обратных или родительских связей применяют Weak: она не увеличивает счётчик владения и не препятствует уничтожению объектов. Это компромисс между удобством совместного владения и необходимостью явно проектировать граф владения.