Go: Функции
Теория: panic и recover
В нормальной жизни программы ошибки — это обычное дело. В Go для этого есть проверка error. Мы открываем файл — его может не быть. Отправляем запрос — сервер может не ответить. Это ожидаемые ситуации, и для них есть явная проверка:
Но бывают ситуации, которые не должны происходить никогда. Если они произошли — значит мир сломался, программа в опасном состоянии. Это и есть место для panic.
Что такое паника
panic — это встроенная функция, которая принимает значение любого типа и запускает аварийный выход. После вызова panic Go начинает «сворачивать» стек вызовов: выходит из функции, выполняет все её defer, поднимается выше и так до самого main. Если за это время никто не перехватит панику, программа завершится с трассировкой.
Почему слово «паника»? Представь пожарную сигнализацию. Когда она сработала, уже неважно, чем ты занимался: нужно бросать всё и эвакуироваться. В Go то же самое: panic — это сигнал о критическом сбое.
Инварианты: когда всё ломается
Чтобы понять, зачем нужен panic, важно знать про инварианты.
Инвариант — это правило, которое должно выполняться всегда. Если оно нарушается — программа становится некорректной.
- Математика. 2 × 2 всегда равно 4. Если вдруг получилось 5 — мир поломался.
- Срезы. Индекс должен быть меньше длины.
- Деление. Нельзя делить на ноль.
Примеры в Go:
В этих случаях возвращать error нет смысла: программа находится в невалидном состоянии. Это именно случай для panic.
Синтаксис panic
value может быть строкой, числом, структурой или error. Чаще всего используют строку или error.
Пример:
Вывод:
recover: как перехватить панику
Когда в коде срабатывает panic, Go включает «режим аварии» и начинает разматывать стек вызовов. На каждом уровне выполняются все defer, объявленные в этой функции. Именно в этот момент и появляется шанс остановить аварию.
recover() работает только внутри defer. Причина простая: только в момент выполнения отложенной функции рантайм находится в «паническом» состоянии. Если вызвать recover где-то в теле функции напрямую, он всегда вернёт nil — паники ведь ещё нет.
Если паника случилась глубоко в коде, без recover процесс просто упадёт. Иногда это нормально (инициализация без ресурса). Но часто лучше не валить всё приложение, а аккуратно перехватить аварию, залогировать и продолжить.
Пример: HTTP-сервер. Один обработчик упал в панику → без recover весь сервер умер. С recover сервер вернёт 500 для конкретного запроса, а остальные запросы продолжат работать.
Минимальный шаблон:
Почему именно так? Потому что только во время выполнения defer рантайм находится в «паническом» состоянии. Если вызвать recover напрямую, он всегда вернёт nil.
Как это работает по шагам
Пример:
Вывод:
Шаги:
- В
crashвызываемpanic. - Go выходит из
crash. Там нетrecover→ идём выше. - В
mainестьdefer, он срабатывает. recoverперехватывает панику, возвращает её значение.- Разматывание стека останавливается, выполнение идёт дальше.
Если нет defer
Вывод:
Без defer панику перехватить негде, программа падает.
Несколько defer
Даже если паника перехвачена, все остальные отложенные вызовы всё равно выполнятся в LIFO-порядке.
Вывод:
Как это работает на практике
Без recover: стек идёт до самого верха и программа падает
Вывод:
Стек полностью свернулся: сначала defer из C, потом из B, потом из A, потом из main. После этого программа упала.
С recover в main: паника перехватывается и программа живёт дальше
Вывод:
Разница очевидна: паника дошла до main, но там в defer сработал recover(). Разматывание стека остановилось, и программа продолжила работу — строка main: end выполнилась.


