go: как defer может незаметно изменить возвращаемое значение
Посмотримка на такую функцию: func getValue() (result int) { defer func() { result++ }() return 10 }
Что она вернёт?
11, а не 10.
Почему? Потому что result - это именованное возвращаемое значение. Когда Go выполняет return 10, сначала присваивает 10 переменной result. Затем запускаются отложенные функции (defer).
Наша функция увеличивает result на единицу.
И только после этого функция действительно возвращает значение.
А теперь немного изменим код: func getValue() int { result := 10 defer func() { result++ }() return result }
Здесь результат будет 10.
Почему? Потому что выражение return result вычисляется до выполнения отложенных функций. Значение копируется в область возврата, и последующее изменение локальной переменной уже не влияет на результат.
А что, если defer меняет ошибку?
Вот здесь поведение становится особенно полезным.
func doWork() (err error) { defer func() { if err == nil { err = errors.New("cleanup failed") } }() return nil }
Функция вернёт:
cleanup failed
Отложенная функция может изменить именованное возвращаемое значение, в том числе error.
Это бывает полезно, когда сама очистка ресурсов может завершиться ошибкой, например при закрытии файла.
Но если использовать такой приём без необходимости, код становится сложнее для понимания.
Ещё один нюанс: когда вычисляются аргументы defer?
Аргументы отложенной функции вычисляются в момент выполнения defer, а не когда сама функция запускается.
Например: func example() { value := 10 defer fmt.Println(value) value = 20 } Результат: 10 А теперь используем замыкание: func example() { value := 10 defer func() { fmt.Println(value) }() value = 20 }
Результат: 20
В первом случае аргумент value вычисляется сразу.
Во втором замыкание захватывает переменную и читает её значение в момент выполнения отложенной функции.
Итак, у defer есть несколько нюансов, о которых легко забыть: -именованные возвращаемые значения можно изменить из defer; -выражения return вычисляются до выполнения отложенных функций; -аргументы отложенной функции вычисляются сразу; -замыкания могут увидеть изменения захваченных переменных.
Самое интересное: две почти одинаковые функции могут вернуть разные значения только из-за того, как объявлен return.