Что такое `panic` в Go? В чем его отличие от ошибок (`error`) и когда его следует использовать?

Ответ

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

Ключевое отличие от error:

  • error: Используется для ожидаемых ошибок, которые являются частью нормальной работы функции (например, файл не найден, ошибка сети). Ошибки в Go — это значения, которые функция возвращает, и вызывающий код должен их обработать.
  • panic: Используется для критических, невосстановимых ошибок, при которых дальнейшее выполнение программы небезопасно или бессмысленно (например, обращение по nil-указателю, выход за пределы массива). Это сигнализирует об ошибке программиста.

Как работает panic:

  1. Выполнение текущей функции немедленно прекращается.
  2. Начинается «раскрутка стека» (stack unwinding) горутины: выполняются все отложенные вызовы (defer).
  3. Если panic не будет перехвачен с помощью recover, программа завершится с выводом сообщения о панике и стека вызовов.

Перехват panic с помощью recover

Функция recover позволяет перехватить панику и возобновить нормальное выполнение. Важно: recover работает только внутри отложенной (defer) функции.

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Перехватили панику:", r)
        }
    }()

    fmt.Println("Вызываем функцию, которая паникует")
    riskyFunction("что-то пошло не так")
    fmt.Println("Эта строка не выполнится")
}

func riskyFunction(message string) {
    panic(message)
}

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

  • Внутри пакета main: Если программа не может запуститься или продолжать работу из-за критической проблемы (например, не удалось прочитать обязательный конфиг).
  • При ошибках программиста: Когда состояние программы становится некорректным из-за логической ошибки, которую невозможно штатно обработать (например, бесконечная рекурсия).

Основное правило: Не используйте panic для обработки обычных ошибок в библиотечном коде. Библиотеки должны возвращать error, предоставляя вызывающему коду решать, как на них реагировать.

Ответ 18+ 🔞

Ну, слушай, смотри, я тебе сейчас про panic в Go расскажу, но так, чтобы ты реально понял, а не просто термины гонял. Это, блядь, не просто функция — это такой крик души программы, когда она понимает, что всё, пиздец, дальше жить нельзя.

Чем это не error?

  • error: Это как когда ты в магазин идёшь, а там картошки нет. Ну, бывает, пошёл в другой. Ошибка, но ожидаемая, с ней можно жить. Функция тебе её возвращает, а ты уже думаешь, что делать.
  • panic: А это когда ты уже на кухне, картошку чистишь, а ножом по пальцу — хрясь! И палец, сука, чуть не отвалился. Вот это пиздец, это уже не до ужина. Дальше готовить опасно, надо всё бросать и скорую вызывать. Это ошибка, после которой нормально работать уже не получится.

Как эта паника работает, эта сука:

  1. Всё, стоп-кран! Текущая функция сразу встаёт как вкопанная.
  2. Начинается "раскрутка стека" — это как когда ты падаешь с лестницы и по пути хватаешься за всё, что видишь (defer-функции как раз успеют отработать).
  3. Если никто не успеет тебя поймать (recover), программа просто сдохнет, выплюнув на экран предсмертное сообщение и всю цепочку вызовов — где, блядь, и что пошло не так.

Как поймать эту летящую в пропасть хрень? recover!

Но recover — хитрая жопа. Работает она только внутри отложенной функции (defer), иначе нихуя не выйдет.

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Перехватили панику:", r) // Вот тут мы её и ловим, ебанашка
        }
    }()

    fmt.Println("Вызываем функцию, которая паникует")
    riskyFunction("что-то пошло не так")
    fmt.Println("Эта строка не выполнится") // Сюда мы уже не попадём, потому что выше — пиздец
}

func riskyFunction(message string) {
    panic(message) // Бдыщь!
}

Так когда же эту панику вызывать, ебать?

  • В main-функции: Когда программа только стартует, а у неё уже всё хуёво — конфиг не прочитался, порт занят. Ну нахуй так жить, лучше сразу умереть.
  • При ошибках программиста: Когда в логике программы случилась такая дичь, что проще всё убить, чем пытаться исправить на ходу. Например, бесконечная рекурсия или обращение к nil. Это сигнал: "Чувак, тут код кривой, иди фикси".

Главное правило, чтобы не быть мудаком: Не используй panic в библиотеках для обычных бытовых ошибок! Ты же не знаешь, какую логику восстановления захочет написать тот, кто твой код использует. Возвращай нормальный error, пусть сам решает — завершаться ему или ещё побороться. Паника — это последнее средство, когда терпения ебать ноль и доверия к состоянию программы — тоже.