Объясните механизм регистрации defer: будет ли он выполнен, если выполнение вышло из области до достижения объявления?
Нет. defer регистрируется только в момент, когда управление действительно дошло до его объявления. Если функция завершилась через return или throw раньше, этот defer не существует для данной активации функции и выполнен не будет.
defer появился как средство централизовать освобождение ресурсов и другие обязательные действия при выходе из области видимости. Он решает проблему дублирования очистки перед каждым возможным return и throw.
Однако defer не является безусловным объявлением действия на весь текст функции. Это инструкция, которая сначала должна быть выполнена, чтобы зарегистрировать отложенное действие.
Ошибка часто возникает, когда разработчик размещает defer в начале логического участка, но ожидает его выполнения даже при выходе до этого участка. Такое ожидание может привести к неверной очистке: например, к попытке закрыть ресурс, который ещё не был открыт.
Нужно различать две ситуации: выход после достижения defer и выход до него. В первой defer выполнится при покидании области, во второй — нет.
Регистрация происходит при последовательном выполнении объявления defer. После регистрации действие запускается при любом обычном выходе из текущей области, включая return и распространение ошибки через throw.
При failEarly == true сообщение не печатается: выполнение не достигло defer. При false defer уже зарегистрирован, поэтому он выполнится во время выхода из функции из-за второй ошибки.
Практическое правило: размещайте defer сразу после успешного получения ресурса или изменения состояния, которое требует отката. Не ставьте очистку до операции, которую она должна обслуживать, если эта операция может завершиться ошибкой раньше.
Если область содержит несколько зарегистрированных defer, они выполняются в обратном порядке регистрации. Для вложенной области действие относится именно к ней и выполняется при выходе из этой области, даже если внешняя функция продолжает работу.
Сервис открывает временный файл только после проверки входных данных. Вариант с defer в самом начале функции формально безопасен лишь тогда, когда очистка умеет обрабатывать неоткрытый файл; иначе возможны лишние вызовы закрытия или некорректные переходы состояния.
Можно явно очищать ресурс перед каждым return и throw. Это прозрачно, но легко пропустить один путь выхода. Можно зарегистрировать defer до проверки — код короче, но очистка должна поддерживать частично выполненную инициализацию.
Лучшее решение — зарегистрировать defer сразу после успешного открытия файла. Тогда очистка выполняется на всех последующих путях выхода и не запускается, если файл открыть не удалось. Это связывает время жизни ресурса с точкой его фактического приобретения.
Вопрос: выполнится ли defer, объявленный внутри вложенного блока, если функция выйдет после завершения этого блока?
Ответ: нет. Он выполняется при выходе из вложенного блока, а не сохраняется до выхода из всей функции. После закрытия блока действие уже отработало.
Вопрос: что произойдёт с зарегистрированным defer, если после него функция выбросит ошибку?
Ответ: defer выполнится во время выхода из функции, после чего ошибка продолжит распространяться к вызывающему коду. Сам defer не перехватывает ошибку автоматически; чтобы изменить её или заменить другой ошибкой, нужна явная логика внутри отложенного действия.
Вопрос: почему размещение defer перед условным приобретением ресурса может быть ошибочным?
Ответ: объявление зарегистрирует очистку независимо от того, был ли ресурс реально получен. В результате очистка может работать с пустым дескриптором, повторно закрывать ресурс или нарушать инварианты состояния. Регистрацию следует выполнять после успешного приобретения ресурса либо сделать очистку явно безопасной для отсутствующего ресурса.