← Назад к вопросам

Как работает defer в Go?

1.0 Junior🔥 211 комментариев
#Основы Go

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает defer в Go

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

Базовое поведение

func example() {
    fmt.Println("1. Начало")
    
    defer fmt.Println("3. defer выполнится при выходе")
    
    fmt.Println("2. Продолжаем работу")
}
// Вывод:
// 1. Начало
// 2. Продолжаем работу
// 3. defer выполнится при выходе

Ключевые особенности

Стек выполнения (LIFO — Last In, First Out):

  • Несколько defer выполняются в обратном порядке
  • Последний defer выполнится первым
func example() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
}
// Вывод:
// 3
// 2
// 1

Аргументы оцениваются немедленно:

  • Значения переменных захватываются в момент объявления defer
  • Это критично помнить при работе с циклами
func example() {
    x := 1
    defer fmt.Println("x =", x) // x будет 1
    x = 2
    fmt.Println("x =", x)        // x будет 2
}
// Вывод:
// x = 2
// x = 1

Проблема в цикле (очень частая ошибка):

// НЕПРАВИЛЬНО
for _, file := range files {
    defer file.Close() // file будет последний файл для всех defer!
}

// ПРАВИЛЬНО
for _, file := range files {
    f := file // создаём локальную копию
    defer f.Close()
}

Использование с return

defer выполняется ДО возврата, но ПОСЛЕ оценки возвращаемого значения:

func getValue() int {
    x := 10
    defer func() {
        x = 20 // это не повлияет на return
    }()
    return x // вернёт 10
}

// Но если return — именованный параметр:
func getValue() (x int) {
    x = 10
    defer func() {
        x = 20 // это повлияет!
    }()
    return // вернёт 20!
}

Частые применения

1. Освобождение ресурсов (files, database connections)

file, err := os.Open("test.txt")
if err != nil {
    return err
}
defer file.Close() // гарантированно закроется

// работа с файлом

2. Работа с блокировками (mutex)

func criticalSection(mu *sync.Mutex) {
    mu.Lock()
    defer mu.Unlock() // гарантированно разблокируется
    
    // critical code
}

3. Трассировка и логирование

func processData() {
    defer fmt.Println("функция завершена")
    defer track(time.Now(), "processData")
    
    // основной код
}

func track(start time.Time, name string) {
    fmt.Printf("%s заняла %v\n", name, time.Since(start))
}

4. Восстановление после panic

func safeFunction() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Восстановились после:", r)
        }
    }()
    
    panic("что-то пошло не так")
}

Влияние на производительность

  • Минимальный overhead если функция простая
  • Значительный overhead в очень критичных по производительности местах
  • В целом не нужно избегать просто так
// Если это критично по производительности
func fastPath(file *os.File) {
    // вместо defer
    // сделай явный Close в конце
}

Анонимные функции в defer

func example() {
    x := 10
    
    // Захватывает указатель на x
    defer func() {
        fmt.Println(x) // выведет 20
    }()
    
    x = 20
}

// Захватывает значение (скопировано)
defer func(value int) {
    fmt.Println(value) // выведет 10
}(x)

Пусть defer не выполнится

func example() {
    defer fmt.Println("defer") // ВЫПОЛНИТСЯ
    
    os.Exit(0) // defer НЕ выполнится — процесс завершится немедленно
}

Best Practices

DO:

  • Используй defer для освобождения ресурсов
  • Помни о LIFO порядке
  • Помни, что аргументы оцениваются сразу
  • В циклах: захватывай значение в переменную

DON'T:

  • Не полагайся на defer для критичного по времени кода
  • Не игнорируй ошибки в defer (логируй!)
  • Не используй defer с os.Exit()

Вывод: defer — это элегантный способ гарантировать выполнение cleanup кода, сохраняя читаемость и безопасность. Это одна из сильных сторон Go.