При раннем возврате через оператор ? как Rust освобождает уже созданные локальные ресурсы?
Оператор ? при ошибке выполняет ранний возврат из функции, поэтому все локальные значения, чьи области видимости завершаются, автоматически уничтожаются. Для типов, реализующих Drop, вызывается drop; локальные переменные обычно уничтожаются в обратном порядке их объявления.
Оператор ? не содержит отдельного механизма освобождения ресурсов: он изменяет поток управления, а очистку выполняют правила владения и областей видимости Rust.
В системных программах ошибка может возникнуть после захвата файла, блокировки, соединения или временного буфера. Язык должен гарантировать освобождение таких ресурсов не только при успешном завершении, но и при любом раннем выходе.
Rust решает эту задачу через владение и детерминированный вызов Drop, не требуя сборщика мусора и ручного дублирования очистки во всех ветвях обработки ошибок.
Функция с несколькими операциями может завершиться ошибкой на любом шаге. Если ресурс освобождать вручную, легко забыть очистку в одной из ветвей, вызвать её дважды или освободить ресурс в неправильном порядке.
При использовании ? ранний возврат особенно легко не заметить: внешне это одна короткая операция, но она завершает текущую функцию. Поэтому важно понимать, какие значения уже вышли из области видимости, какие были перемещены, а какие продолжают принадлежать другому владельцу.
Упрощённо ? проверяет Result. При Ok он извлекает успешное значение и продолжает выполнение, а при Err возвращает ошибку из текущей функции. Такой возврат завершает область видимости функции и запускает обычную процедуру уничтожения локальных значений.
При выполнении Err("сбой")? сначала уничтожается second, затем first. Это происходит потому, что они принадлежат функции work, а их области видимости завершаются при раннем возврате.
Порядок определяется не самим ?, а правилами завершения областей видимости. Если значение было перемещено до ошибки, прежний владелец его уже не уничтожает; если ресурс помещён в ManuallyDrop или намеренно забыт через mem::forget, автоматического вызова Drop не будет.
Drop не предназначен для обработки ошибки освобождения: метод drop не возвращает Result. Поэтому операции вроде фиксации транзакции обычно выполняют явно, а защитный объект используют для автоматического отката при выходе без успешного завершения.
Функция открывает файл блокировки, затем читает конфигурацию через несколько операций с ?. Вариант с ручным unlock требует повторять освобождение в каждой ветви и легко оставляет блокировку при новом раннем возврате.
Вариант с защитным объектом и реализацией Drop надёжно снимает блокировку при любом выходе. Его минус в том, что Drop не может сообщить вызывающему коду об ошибке снятия блокировки, поэтому критические или потенциально ошибочные завершающие действия нужно предоставить отдельным явным методом.
Практичное решение — RAII-защитник: он освобождает ресурс при обычном выходе и при ?, а успешное завершение переводит его в состояние «не освобождать повторно». В результате код короче, очистка единообразна, а операции, требующие обработки ошибки, остаются явными.
? вызывает Drop у всех локальных значений функции?Нет. Drop вызывается только для значений, которые всё ещё принадлежат соответствующей области видимости и не были перемещены. Перемещённое значение уничтожается новым владельцем, а значения с подавленным или вручную контролируемым уничтожением могут не пройти обычный путь Drop.
Учитываются области видимости. Значения во вложенной области уничтожаются при выходе из неё, а локальные значения одной области обычно — в обратном порядке объявления. Поэтому нельзя полагаться только на порядок вызовов ?: нужно учитывать, где именно создан каждый объект и не был ли он перемещён.
Drop для возврата ошибки освобождения через Result?Нет. Drop::drop не возвращает результат, а ошибка из деструктора не может быть передана вызывающему коду обычным способом. Если освобождение само по себе может завершиться ошибкой и это важно для корректности, его следует выполнить явным методом до выхода из функции; Drop можно оставить резервным механизмом очистки.