recover: поймать панику
Команда поставила перехват на границах обработчиков, и сервис перестал падать целиком. Но первый перехват молча возвращал пустоту: проверка была спрятана в отдельную функцию. Миша перенёс её наверх и снова увидел причину падения.
recover останавливает панику, но работает только при вызове непосредственно внутри defer-функции той горутины, где паника случилась.
recover — функция, которая ловит панику. Работает она только внутри defer-функции, которая выполняется во время разматывания стека.
Превратить панику в ошибку
func safe() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("паника: %v", r)
}
}()
doSomethingRisky()
return nil
}
Когда doSomethingRisky паникует, выполняется defer: recover возвращает значение, переданное в panic, паника останавливается, и функция safe возвращается — с err, в которую упакована причина. Вызывающий обрабатывает её как обычную ошибку.
Только в defer, и только напрямую
recover, вызванный в любом другом месте, просто вернёт nil и ничего не сделает. И есть ловушка: recover должен вызываться непосредственно в теле defer-функции. Имя функции при этом не важно: defer handle работает, потому что сама handle — отложенная функция, и recover внутри неё вызывается напрямую. Ломает всё вложенный вызов — лишняя функция между defer и recover:
func safe() {
defer func() { handle() }() // неправильно
doSomethingRisky()
}
func handle() {
recover() // вернёт nil — паника не остановится
}
recover видит панику, только если вызван непосредственно в теле отложенной функции, которую разматывают; здесь он на один вызов глубже — между ним и defer стоит ещё одна функция.
Только своя горутина
Ещё один предел: recover ловит панику только в той горутине, где она случилась. Паника в другой горутине — смерть всего процесса:
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Поэтому в каждой горутине, которая может упасть, должен быть свой recover — обычно в обёртке рядом с go.
Вы узнаете:
- где recover оправдан: границы обработчиков и обёртки над чужим кодом
- почему recover не для управления потоком
Проверьте себя
recover() вызван вне defer, обычной строкой в функции. Что произойдёт?
defer func() { handle() }(), где handle() вызывает recover(). Поймает ли панику?
Паника случилась в горутине, запущенной через go. Поймает ли её recover в main?
правильных ответов
recover живёт только в defer той же горутины и должен вызываться непосредственно в теле отложенной функции: поймать панику, превратить её в ошибку и отдать вызывающему.
10 000 ₽ · 60 минут · онлайн