Комментарии (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.