Что происходит с локальными владельцами при панике во время раскрутки стека?
При панике в режиме раскрутки стека Rust автоматически вызывает Drop для уже созданных локальных значений-владельцев по мере выхода из их областей видимости. Поэтому ресурсы освобождаются даже при аварийном завершении обычного пути выполнения. Значения, которые были перемещены или ещё не успели создаться, повторно не уничтожаются.
Такой подход продолжает идею детерминированного управления ресурсами, известную как RAII: время жизни ресурса связывается с временем жизни объекта-владельца. В Rust это особенно важно, поскольку язык не использует сборщик мусора для обычного управления памятью.
Паника является нетипичным путём выхода из функции, но сама по себе не должна превращать освобождение памяти и ресурсов в исключение из правил владения. Автоматический вызов деструкторов позволяет сохранять эти гарантии и при аварийном распространении ошибки по стеку.
Если функция завершится паникой после захвата файла, блокировки или выделения памяти, ручная очистка может не выполниться. Это приводит не обязательно к утечке памяти, но может оставить ресурс занятым, данные — незаписанными, а состояние программы — неконсистентным.
В Rust важно различать владение и заимствование: уничтожается владелец, а не каждая ссылка на значение. Перемещённое значение уже не принадлежит прежней переменной, поэтому Rust не пытается уничтожить его дважды.
При раскрутке стека Rust покидает области видимости в процессе распространения паники. Для каждого полностью инициализированного владельца, который выходит из области видимости, вызывается его реализация Drop, после чего освобождаются содержащиеся в нём значения.
При обычной раскрутке стека Guard::drop будет вызван до завершения распространения паники. Если значение было перемещено в другую переменную, владельцем становится новая переменная, и именно она отвечает за последующий вызов Drop.
Это не означает, что любой процесс восстановления гарантирован. При стратегии panic = abort программа немедленно завершается, поэтому пользовательские деструкторы не вызываются. Операционная система обычно освобождает память процесса, но внешние ресурсы, например незаписанные данные или протокол взаимодействия с устройством, нельзя считать корректно обработанными.
Паника внутри Drop во время уже выполняющейся раскрутки стека является критической ситуацией и обычно приводит к немедленному аварийному завершению. Поэтому Drop должен выполнять ограниченную и надёжную очистку, а не запускать потенциально паникующую бизнес-логику.
Сервис получает блокировку, изменяет состояние и затем вызывает несколько функций, одна из которых может запаниковать. Ручной вызов разблокировки в конце функции ненадёжен: при панике управление до него не дойдёт.
Возможные варианты:
catch_unwind вокруг каждой операции — позволяет перехватывать панику, но усложняет архитектуру и не заменяет корректное управление ресурсом.Drop — связывает освобождение блокировки с областью видимости и работает при обычном возврате и при раскрутке стека.Практически выбирают третий вариант: объект-владелец освобождает ресурс в Drop, а панику перехватывают только на специально выбранных границах, например на границе задачи или плагина. Если приложение не допускает раскрутку стека, можно выбрать panic = abort, но тогда очистка через Drop при панике недоступна.
1. Вызывается ли Drop для значения, которое было перемещено перед паникой?
Нет, прежняя переменная больше не является владельцем и не уничтожает перемещённое значение. Drop будет вызван для нового владельца, если тот уже существует и выходит из области видимости при раскрутке. Это предотвращает двойное освобождение ресурса.
2. Что произойдёт с локальной переменной, инициализация которой не завершилась до паники?
Она не считается полностью созданным значением, поэтому её деструктор не вызывается. Rust отслеживает инициализацию на уровне мест хранения: уничтожаются только те значения, которые действительно были успешно созданы.
3. Гарантирует ли раскрутка стека корректное восстановление внешнего ресурса?
Нет. Она гарантирует выполнение деструкторов Rust для созданных владельцев, но результат зависит от самого Drop. Например, деструктор может закрыть файл, однако это не гарантирует успешную запись всех буферизованных данных или согласованность удалённой транзакции. Для таких ресурсов нужны явные протоколы подтверждения, отката и обработки ошибок, а Drop обычно оставляют для гарантированной базовой очистки.