defer, panic и recover

defer откладывает вызов, panic роняет программу, recover её спасает. Тонкий, но важный механизм.

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

defer: гарантированная уборка

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

package main

import "fmt"

func main() {
    fmt.Println("начало")
    defer fmt.Println("уборка (отложено)")
    fmt.Println("работа")
}

Вывод:

начало
работа
уборка (отложено)

Несколько defer выполняются в обратном порядке (LIFO, как стек). Типичный паттерн с файлом:

f, err := os.Open("data.txt")
if err != nil {
    return err
}
defer f.Close() // закроется при любом выходе из функции

panic: что-то пошло совсем не так

panic прерывает нормальное выполнение и начинает разматывать стек, попутно выполняя отложенные defer. Если панику не перехватить — программа аварийно завершится. Паникой пользуются редко: только для действительно невосстановимых ситуаций (баг в программе, невозможное состояние). Обычные ошибки — это error, а не паника.

recover: перехват паники

recover работает только внутри отложенной функции. Он останавливает разматывание стека и возвращает значение паники. Это нужно, например, чтобы один упавший обработчик не уронил весь сервер.

package main

import "fmt"

func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("восстановлено после паники: %v", r)
        }
    }()
    result = a / b // деление на 0 вызовет панику
    return result, nil
}

func main() {
    res, err := safeDivide(10, 0)
    fmt.Println(res, err)

    res, err = safeDivide(10, 2)
    fmt.Println(res, err)
}

Вывод:

0 восстановлено после паники: runtime error: integer divide by zero
5 <nil>

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

МеханизмНазначение
deferотложить вызов до выхода из функции (уборка)
panicаварийно прервать выполнение (редко)
recoverперехватить панику внутри defer

Итог

  • defer гарантирует уборку ресурсов при любом выходе; несколько — в обратном порядке.
  • panic — только для невосстановимых ситуаций; обычные сбои — это error.
  • recover внутри defer перехватывает панику, например чтобы не ронять сервер.
Проверьте себя
1. Когда выполнится функция, помеченная defer?
AСразу же
BПри выходе из окружающей функции (через return или панику)
CТолько при ошибке
DВ начале программы
2. Когда уместно использовать panic?
AДля любой ошибки ввода
BДля невосстановимых ситуаций; обычные ошибки — это error
CВместо return
DДля вывода в лог
3. Где работает recover?
AВ любом месте кода
BТолько внутри отложенной (defer) функции
CТолько в main
DВ блоке if
Поддержать проект