Defer — это уникальный механизм Go, позволяющий выполнить функцию после завершения текущей функции, даже при возникновении panic. Исторически это аналог on-exit конструкций, но в Go реализовано как стек deferred вызовов. Важный нюанс — параметры deferred функции вычисляются сразу в момент объявления defer, не в момент её реального вызова!
Проблема — неочевидное поведение: можно ожидать, что параметры передаются при срабатывании defer, но это не так. Это часто приводит к багам при работе с изменяемыми или внешними переменными.
Решение — всегда иметь в виду, что параметры вычисляются немедленно, а эффект deferred вызова наступает потом.
Пример кода:
func f() { x := 5 defer fmt.Println(x) x = 10 } // Выведет: 5, а не 10
Ключевые особенности:
Что выведет следующий код? Почему?
func main() { i := 0 defer fmt.Println(i) i = 1 }
Ответ: выведет 0. Аргумент функции fmt.Println сохраняется сразу при объявлении defer.
Влияет ли изменение переменной после объявления defer на передачу её значения в функцию?»
Нет, не влияет — вычисление происходит при объявлении defer:
defer fmt.Println(x) // Значение x сохраняется сейчас, не потом
Можно ли сделать defer, чтобы финально вывести последнее состояние переменной?
Да, с помощью анонимной функции (closure):
defer func() { fmt.Println(x) }() // захватит актуальное значение x в момент deferred вызова
Код вызывается так:
var f *os.File // ... defer f.Close()
Но f присваивается позже, поэтому panic от вызова nil pointer!
Плюсы:
Минусы:
Обёртывание чистящего действия через анонимную deferred функцию с проверкой:
defer func() { if f != nil { f.Close() } }()
Плюсы:
Минусы: