Объясните механизм `panic`, `defer` и `recover` в Go. В каких случаях их следует использовать?

Ответ

Это механизм Go для обработки исключительных, непредвиденных ситуаций, которые делают дальнейшее выполнение программы невозможным в текущем контексте.

  • panic — это встроенная функция, которая останавливает обычное выполнение текущей горутины. Когда функция вызывает panic, её выполнение прекращается, все отложенные (defer) вызовы в этой функции выполняются, после чего функция возвращает управление вызвавшей её функции, где происходит то же самое. Процесс продолжается вверх по стеку вызовов, пока программа не завершится аварийно.

  • defer — это ключевое слово, которое откладывает выполнение вызова функции до тех пор, пока содержащая её функция не завершит свое выполнение (либо через return, либо из-за panic). Отложенные вызовы выполняются в порядке LIFO (Last-In, First-Out) — последняя отложенная функция выполнится первой.

  • recover — это встроенная функция, которая позволяет перехватить управление горутиной, находящейся в состоянии паники. recover полезна только внутри defer-функций. Если текущая горутина не паникует, вызов recover вернет nil. Если же горутина паникует, вызов recover перехватит значение, переданное в panic, и восстановит нормальное выполнение.

Пример:

func main() {
    fmt.Println("Start")
    safeDivide(10, 0)
    fmt.Println("End")
}

func safeDivide(a, b int) {
    defer func() {
        // recover работает только внутри defer
        if r := recover(); r != nil {
            fmt.Printf("Перехвачена паника: %vn", r)
        }
    }()

    result := a / b // Эта строка вызовет панику
    fmt.Printf("Результат: %dn", result) // Эта строка не выполнится
}

Вывод:

Start
Перехвачена паника: runtime error: integer divide by zero
End

Когда использовать:

Основное правило — не использовать panic для обычной обработки ошибок. Для этого в Go есть явный возврат error.

Допустимые случаи использования panic/recover:

  1. Предотвращение падения всего сервера. Например, в веб-сервере одна горутина, обрабатывающая HTTP-запрос, может запаниковать из-за непредвиденной ошибки. С помощью recover в defer можно перехватить эту панику, записать лог, отдать клиенту ошибку 500, но при этом сам сервер продолжит работать и обслуживать другие запросы.
  2. Ошибки в программе, которые не должны происходить. Например, если программа достигает состояния, которое считается невозможным (например, case в switch, который никогда не должен сработать). Это сигнал о серьезной ошибке в логике программиста.

Ответ 18+ 🔞

А, слушай, смотри, вот эта штука в Go — panic, defer, recover — это, блядь, как аварийный люк в подводной лодке, только для кода. Когда всё пошло по пизде и обычным путём уже не выкрутиться.

Представь: твоя программа плывёт себе спокойно, и тут panic — это как крик "ЁБАНА-А-АТЬ!" внутри горутины. Всё, стоп, приехали. Выполнение резко обрывается, и начинается, сука, цепная реакция: функция, которая запаниковала, начинает сворачиваться, но перед этим выполняет все отложенные дела — те самые defer-вызовы. Потом эта паника, как волна дерьма, идёт наверх по стеку, ко всем, кто её вызывал, и так до тех пор, пока вся программа не накроется медным тазом с аварийным логом.

А defer — это типа "сделай потом, чувак". Захотел закрыть файл, освободить мьютекс или просто выпить пива после работы — вешаешь defer, и эта хуйня выполнится гарантированно, когда функция будет заканчиваться. Неважно, как — штатно через return или потому что её, блядь, паникой накрыло. И что самое важное — выполнятся они в обратном порядке, как тарелки у циркача: последний defer — первый на выходе.

И вот тут появляется наш спасатель — recover. Это как сетка под трапецией. Работает он ТОЛЬКО внутри этих самых defer-функций. Если горутина спокойна, recover вернёт nil, типа "всё чисто, расслабься". Но если она в панике, то recover перехватит то самое значение, которое крикнули в panic, и, внимание, ВОССТАНОВИТ нормальную работу. Паника остановится, и можно будет жить дальше.

Вот, смотри, наглядный пиздец:

func main() {
    fmt.Println("Начало, всё ок")
    safeDivide(10, 0) // Сейчас начнётся...
    fmt.Println("Конец, мы живы!") // И эта строка ВЫПОЛНИТСЯ!
}

func safeDivide(a, b int) {
    defer func() {
        // recover работает ТОЛЬКО тут, внутри defer!
        if r := recover(); r != nil {
            fmt.Printf("Перехватили панику, сука: %vn", r) // Ловим крик "деление на ноль!"
        }
    }()

    result := a / b // На этой строке — ПИЗДЕЦ, runtime error: integer divide by zero
    fmt.Printf("Результат: %dn", result) // Сюда мы уже никогда не попадём
}

Что на экране:

Начало, всё ок
Перехватили панику, сука: runtime error: integer divide by zero
Конец, мы живы!

Главное правило, блядь, выучи как "Отче наш":

НЕ ИСПОЛЬЗУЙ panic ДЛЯ ОБЫЧНЫХ ОШИБОК! Для этого есть error, возвращай его явно и обрабатывай. Это основа идиомы Go.

А panic/recover — это для совсем уж крайних, ебаных случаев:

  1. Чтобы сервер не лег. Допустим, в горутине, обрабатывающей HTTP-запрос, случилось что-то невообразимое. Вместо того чтобы падать всему процессу, мы в defer на уровне хендлера ставим recover, логируем жуткую ошибку, клиенту отдаём 500 Internal Server Error, а сервер — охуенно! — продолжает работать и обслуживать других.
  2. Ошибки, которых в принципе быть не должно. Типа default: в switch, который никогда не должен сработать, или получение элемента из мапы, которая гарантированно должна его содержать. Это крик "Я, блядь, как программист, облажался, тут чушь какая-то!" — вот для такого panic в самый раз.