Модуль
1Основы функций2Variadic-функции3Замыкания4defer и panic/recover← вы здесь
Урок 4~12 минут

defer и panic/recover

defer — гарантированный cleanup

defer откладывает выполнение функции до момента возврата из текущей функции. Выполняется всегда — при нормальном return и при panic.

go
func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // закроется в любом случае
 
    // работаем с файлом...
    return nil
}

Без defer пришлось бы писать f.Close() перед каждым return.


LIFO-порядок

Несколько defer выполняются в обратном порядке (последний объявленный — первым):

go
func example() {
    defer fmt.Println("третий")
    defer fmt.Println("второй")
    defer fmt.Println("первый")
}
 
// Вывод:
// первый
// второй
// третий

Это удобно для вложенных ресурсов: открыл A, открыл B → при закрытии сначала B, потом A.


Аргументы вычисляются сразу

Аргументы функции в defer вычисляются в момент объявления, не выполнения:

go
x := 1
defer fmt.Println(x) // захватывает x = 1
x = 2
// При return напечатает: 1

Чтобы захватить значение на момент выполнения — используй замыкание:

go
x := 1
defer func() { fmt.Println(x) }() // захватит x при выполнении
x = 2
// При return напечатает: 2

defer и именованные возвращаемые значения

defer может изменить возвращаемое значение через именованные переменные:

go
func double(n int) (result int) {
    defer func() {
        result *= 2 // модифицирует result перед возвратом
    }()
    result = n
    return
}
 
fmt.Println(double(5)) // 10

Это используется для оборачивания ошибок и добавления контекста.


panic — критическая ошибка

panic останавливает нормальное выполнение и начинает «раскручивать стек» — выполняет все defer по пути вверх:

go
func mustPositive(n int) int {
    if n <= 0 {
        panic(fmt.Sprintf("ожидалось положительное число, получено %d", n))
    }
    return n
}

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


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

recover() останавливает раскрутку стека и возвращает значение, переданное в panic. Работает только внутри defer:

go
func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("перехвачена паника: %v", r)
        }
    }()
    return a / b, nil // паникует при b = 0
}
 
result, err := safeDiv(10, 0)
fmt.Println(result, err) // 0 перехвачена паника: runtime error: ...

Типичные паттерны с defer

Мьютекс:

go
mu.Lock()
defer mu.Unlock()

Транзакция:

go
tx, _ := db.Begin()
defer func() {
    if err != nil {
        tx.Rollback()
    } else {
        tx.Commit()
    }
}()

Логирование времени выполнения:

go
func timed(name string) func() {
    start := time.Now()
    return func() {
        fmt.Printf("%s took %v\n", name, time.Since(start))
    }
}
 
defer timed("loadUsers")()

Обрати внимание на двойные скобки ()() — первые вызывают timed (захватывая start), вторые — регистрируют возвращённую функцию как defer.

defer выполняется в LIFO-порядке: последний defer — первым. Это как стек «отложенных дел».
// Go-программа
func example() {
defer fmt.Println("первый")
defer fmt.Println("второй")
defer fmt.Println("третий")
// ... тело функции
}
Стек defer
пусто
Вывод
_
Defers выполняются в порядке LIFO (последний добавлен — первый выполнен). Последний defer выполняется первым, первый — последним.
🎯
Миссия 1 из 4
В каком порядке выполняются несколько defer в одной функции?