Исторически концепция defer была введена в Go для безопасного освобождения ресурсов и финализации действий вне зависимости от того, как завершилось выполнение функции (обычно или по panic). Однако взаимодействие defer с return и panic имеет несколько досадных ловушек, которые часто упускают из виду даже опытные разработчики.
Проблема в том, что порядок вычисления возвращаемых значений, работа named return values и изменение этих значений в defer сильно отличается от привычного поведения во многих языках. Кроме того, ошибки могут возникнуть, если в defer присутствует попытка изменить уже вычисленные значения, вызывая неожиданное поведение.
Решение — всегда помнить: значения, которые возвращает функция, вычисляются ДО запуска defer, но если используются именованные результаты, их можно изменить внутри defer до фактического возврата из функции.
Пример кода:
func tricky() (res int) { defer func() { res = 42 // Меняет возвращаемое значение! }() return 10 } func main() { fmt.Println(tricky()) // Выведет 42, а не 10 }
Ключевые особенности:
В каком порядке выполняются отложенные (defer) функции?
Они выполняются строго в обратном порядке их объявления (stack — LIFO).
func f() { defer fmt.Println("1") defer fmt.Println("2") } // Выведет: 2, затем 1
Когда именно вычисляются параметры для defer-функций — в момент объявления defer или при его выполнении?
Параметры для defer-функции вычисляются в момент объявления defer, а не при вызове.
func f() { i := 1 defer fmt.Println(i) // будет выведено 1, даже если i изменится позже i = 2 }
Может ли defer изменить неименованный результат функции?
Нет. Только именованные возвращаемые значения можно менять в defer.
func f() int { defer func() { /* ничего не изменить */ }() return 5 }
Молодой разработчик хотел в defer залогировать код возврата и по ошибке изменил именованное возвращаемое значение, тем самым "протёр" реальный результат функции.
Плюсы:
Минусы:
В другой ситуации defer использовался только для освобождения ресурсов, логирования и не менял return, а важные значения явно назначались перед return.
Плюсы:
Минусы: