После успешного первого вызова recover в той же deferred функции что вернёт второй вызов?

После успешного первого вызова recover в той же deferred-функции что вернёт второй вызов?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Второй вызов recover вернёт nil. Первый успешный вызов извлекает значение паники и прекращает раскрутку стека; повторно получить ту же панику нельзя.

Исторический контекст

В Go обычные ожидаемые сбои представлены значениями error, а panic/recover предназначены для аварийного управления потоком и восстановления на границе компонента. Механизм recover сделан одноразовым относительно конкретной паники: обработчик получает её значение, после чего паника считается перехваченной.

Постановка проблемы

Разработчик может попытаться вызвать recover повторно — например, сначала для логирования, а затем для преобразования паники в ошибку. Это приводит к тому, что второй вызов получает nil, поэтому исходное значение уже нельзя использовать для другой обработки.

Важно также не путать продолжение deferred-функции с продолжением исходной функции: после успешного recover deferred-функция может выполнять следующие инструкции, но выполнение не возвращается к месту возникновения паники.

Подробное решение

recover возвращает значение только при одновременном выполнении условий: вызов происходит внутри deferred-функции, эта deferred-функция выполняется во время раскрутки стека из-за паники, и вызов является непосредственным. При успешном вызове раскрутка прекращается, а значение паники передаётся вызывающему коду.

После этого состояние паники уже поглощено. Поэтому следующий вызов recover в той же deferred-функции выполняется вне активной раскрутки и возвращает nil.

package main import "fmt" func main() { defer func() { first := recover() second := recover() fmt.Printf("first=%v, second=%v ", first, second) }() panic("сбой") }

Здесь первый вызов получает строку сбой, а второй — nil. Если нужно и записать панику в журнал, и преобразовать её в error, значение следует сохранить в локальной переменной после единственного вызова recover.

Нужно учитывать, что nil как результат сам по себе неоднозначен для некоторых сценариев: паника с нулевым значением имеет специальные особенности, зависящие от версии Go. Поэтому обработчик не должен строить сложную логику на повторных вызовах recover; он должен один раз получить значение и сразу классифицировать его.

Ситуация из практики

В HTTP middleware требуется перехватывать паники, логировать их и возвращать клиенту безопасную внутреннюю ошибку. Вариант с двумя вызовами recover кажется естественным: первый используется для лога, второй — для формирования ответа, но второй вызов всегда теряет исходное значение.

Можно вызвать recover один раз и передать сохранённое значение двум независимым операциям. Можно также сразу преобразовать значение в диагностическую структуру, сохранив исходное значение отдельно; это удобнее, если логирование и формирование ответа должны иметь разные форматы.

Выбранный вариант — один вызов recover с сохранением результата в локальной переменной. Он исключает потерю причины, позволяет безопасно отделить внутреннюю диагностику от внешнего ответа и делает порядок обработки очевидным. Клиент получает обобщённую ошибку, а журнал — исходное значение и контекст запроса.

Что кандидаты часто упускают

  1. Можно ли вызвать recover в условии, а затем использовать его результат повторно?

Нет, такой подход всё равно вызывает recover только один раз в условии, а результат нужно сохранить. Если вместо сохранённого значения повторно вызвать recover, второй вызов вернёт nil, потому что паника уже перехвачена.

  1. Продолжится ли после успешного recover выполнение инструкций в той же deferred-функции?

Да. Deferred-функция продолжит выполняться с инструкции после вызова recover. Однако исходная функция не возобновится с места паники: её обычный возврат возможен только через завершение deferred-обработки и корректно организованные возвращаемые значения.

  1. Что произойдёт, если после успешного recover deferred-функция сама вызовет panic?

Это будет уже новая паника. Успешный recover первой паники не создаёт возможности повторно получить её и не защищает deferred-функцию от последующих сбоев. Новую панику сможет перехватить только внешний подходящий deferred-обработчик; если его нет, программа завершится с новой паникой.