Материал проходит вычитку — возможны неточности.
Урок 56 из 118

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 ловит панику только в той горутине, где она случилась. Паника в другой горутине — смерть всего процесса:

Go

Поэтому в каждой горутине, которая может упасть, должен быть свой recover — обычно в обёртке рядом с go.

В следующем уроке

Вы узнаете:

  • где recover оправдан: границы обработчиков и обёртки над чужим кодом
  • почему recover не для управления потоком
Задание · вопросы

Проверьте себя

Вопрос 1 из 3

recover() вызван вне defer, обычной строкой в функции. Что произойдёт?

Вопрос 2 из 3

defer func() { handle() }(), где handle() вызывает recover(). Поймает ли панику?

Вопрос 3 из 3

Паника случилась в горутине, запущенной через go. Поймает ли её recover в main?

Итоги

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

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки