После успешного первого вызова recover в той же deferred-функции что вернёт второй вызов?
Второй вызов recover вернёт nil. Первый успешный вызов извлекает значение паники и прекращает раскрутку стека; повторно получить ту же панику нельзя.
В Go обычные ожидаемые сбои представлены значениями error, а panic/recover предназначены для аварийного управления потоком и восстановления на границе компонента. Механизм recover сделан одноразовым относительно конкретной паники: обработчик получает её значение, после чего паника считается перехваченной.
Разработчик может попытаться вызвать recover повторно — например, сначала для логирования, а затем для преобразования паники в ошибку. Это приводит к тому, что второй вызов получает nil, поэтому исходное значение уже нельзя использовать для другой обработки.
Важно также не путать продолжение deferred-функции с продолжением исходной функции: после успешного recover deferred-функция может выполнять следующие инструкции, но выполнение не возвращается к месту возникновения паники.
recover возвращает значение только при одновременном выполнении условий: вызов происходит внутри deferred-функции, эта deferred-функция выполняется во время раскрутки стека из-за паники, и вызов является непосредственным. При успешном вызове раскрутка прекращается, а значение паники передаётся вызывающему коду.
После этого состояние паники уже поглощено. Поэтому следующий вызов recover в той же deferred-функции выполняется вне активной раскрутки и возвращает nil.
Здесь первый вызов получает строку сбой, а второй — nil. Если нужно и записать панику в журнал, и преобразовать её в error, значение следует сохранить в локальной переменной после единственного вызова recover.
Нужно учитывать, что nil как результат сам по себе неоднозначен для некоторых сценариев: паника с нулевым значением имеет специальные особенности, зависящие от версии Go. Поэтому обработчик не должен строить сложную логику на повторных вызовах recover; он должен один раз получить значение и сразу классифицировать его.
В HTTP middleware требуется перехватывать паники, логировать их и возвращать клиенту безопасную внутреннюю ошибку. Вариант с двумя вызовами recover кажется естественным: первый используется для лога, второй — для формирования ответа, но второй вызов всегда теряет исходное значение.
Можно вызвать recover один раз и передать сохранённое значение двум независимым операциям. Можно также сразу преобразовать значение в диагностическую структуру, сохранив исходное значение отдельно; это удобнее, если логирование и формирование ответа должны иметь разные форматы.
Выбранный вариант — один вызов recover с сохранением результата в локальной переменной. Он исключает потерю причины, позволяет безопасно отделить внутреннюю диагностику от внешнего ответа и делает порядок обработки очевидным. Клиент получает обобщённую ошибку, а журнал — исходное значение и контекст запроса.
Нет, такой подход всё равно вызывает recover только один раз в условии, а результат нужно сохранить. Если вместо сохранённого значения повторно вызвать recover, второй вызов вернёт nil, потому что паника уже перехвачена.
Да. Deferred-функция продолжит выполняться с инструкции после вызова recover. Однако исходная функция не возобновится с места паники: её обычный возврат возможен только через завершение deferred-обработки и корректно организованные возвращаемые значения.
Это будет уже новая паника. Успешный recover первой паники не создаёт возможности повторно получить её и не защищает deferred-функцию от последующих сбоев. Новую панику сможет перехватить только внешний подходящий deferred-обработчик; если его нет, программа завершится с новой паникой.