После того как defer перехватил panic, продолжится ли выполнение функции с места сбоя?
Нет. После успешного вызова recover раскрутка паники прекращается, но выполнение функции не возобновляется с места сбоя: функция завершается и возвращает управление вызывающему коду. Если у функции есть именованные результаты, наружу передаются их текущие значения; если они не были явно установлены, обычно это нулевые значения.
В Go обычные ожидаемые ошибки принято возвращать через error, чтобы вызывающий код явно обработал результат операции. panic и recover предназначены для другого сценария: аварийного нарушения инварианта или для защиты границы, где необходимо не допустить падения всего процесса.
Механизм recover позволяет такой границе преобразовать панику в контролируемый результат, но не делает панику обычным переходом управления. Это важно для предсказуемости: функция, в которой произошёл сбой, не продолжает работу в потенциально повреждённом состоянии.
Представим функцию с именованными результатами. Внутри неё произошла паника, а отложенная функция её перехватила и записала ошибку в именованный результат.
Ошибочное ожидание состоит в том, что после recover выполнение продолжится со следующей строки. Если бы это было так, код после точки сбоя мог бы использовать частично изменённые данные, повторно освободить ресурс или нарушить инварианты.
recover работает только при вызове из отложенной функции, выполняемой во время раскрутки стека из-за паники. Если он получает активную панику, раскрутка прекращается, а функция, в которой сработал defer, завершается как обычный возврат.
Точка сбоя не является точкой возобновления. Поэтому операторы после вызвавшего панику выражения не выполняются. Отложенные функции продолжают выполняться по обычным правилам, а затем вызывающий код получает результаты завершившейся функции.
Именованные результаты удобны для преобразования паники в ошибку: defer может присвоить значение переменной err, после чего оно будет возвращено. Это не означает, что любые частичные результаты безопасны; их следует устанавливать только если контракт функции допускает такой результат.
После panic строка с обычным продолжением функции не выполнялась бы. В примере recover записывает ошибку, и load возвращает текущие значения именованных результатов: частичное значение и сформированную ошибку.
Преобразовывать любую панику в error без ограничений опасно: можно скрыть дефект программы и продолжить работу с некорректным состоянием. Обычно такую границу размещают в обработчике запроса, в обёртке запуска горутины или внутри специально изолированного адаптера, а саму панику после регистрации делают наблюдаемой.
В сервере обработчик вызывает сторонний конвертер, который иногда паникует на повреждённом входе. Если не перехватить панику на границе запроса, можно завершить обработку горутины аварийно и получить отказ в обслуживании.
Рассматривались два варианта. Первый — исправить все места вызова и проверять каждую возможную причину заранее; это сохраняет прозрачность ошибок, но невозможно, если источник находится во внешней библиотеке. Второй — обернуть вызов в defer с recover; это защищает границу, но требует не скрывать панику и корректно формировать ответ об ошибке.
Выбран второй вариант только на внешней границе: паника преобразуется в ошибку запроса, записывается в журнал с контекстом, а выполнение после точки сбоя не продолжается. Внутренний код по-прежнему использует обычные error, поэтому дефекты не маскируются повсеместным recover.
Вопрос: Может ли defer изменить возвращаемое значение после recover?
Ответ: Да, если результат функции именованный. Отложенная функция имеет доступ к этим переменным и может присвоить им итоговые значения перед завершением функции. Для неименованных результатов такой способ управления результатом недоступен напрямую, поэтому для преобразования паники обычно используют именованные результаты или отдельную обёртку.
Вопрос: Выполнится ли код вызывающей функции после возврата из функции, в которой сработал recover?
Ответ: Да. После завершения функции с перехваченной паникой управление возвращается её вызывающему коду, и тот продолжает выполнение с точки после вызова, если сам не находится в состоянии паники. Это отличается от продолжения внутри функции, где произошла паника: её оставшаяся часть уже не выполняется.
Вопрос: Что произойдёт, если recover вызвать вне активной паники?
Ответ: Он вернёт nil и не изменит управление. Вызов в обычной функции, а также вызов из defer, который выполняется не во время раскрутки паники, не перехватывает будущие или уже завершившиеся паники. Поэтому проверка recover имеет смысл именно внутри отложенного обработчика, который может выполняться при аварийном завершении.