func sum(a, b int) (c int) {
с = a + b
defer func() {
с++
}()
return
}
Что вернет эта функция в случае
sum(5, 5)? Для многих будет очевидно, что 11 - записали в c сумму, и в дефере инкрементировали. Окей, а если так?
func sum(a, b int) (c int) {
result := a + b
defer func() {
result++
}()
return result
}
казалось бы, ситуация практически 1 в 1. А нет: здесь дефер уже никак не влияет на возвращенный результат сложения. Почему так?
Давайте рассмотрим еще один, показательный случай:
func sum(a, b int) (c int) {
result := a + b
defer func() {
с++
}()
return result
}
Что мы имеем? Складываем а и б в переменную result, после чего возвращаем ее. А в дефере мы инкрементируем, казалось бы, ничего общего не имеющую с результатом возврата переменную с. Но что мы видим?
Ba-dum-tsss. Имеем снова
sum(5, 5) == 11! Вот это уже интересно. А теперь давайте разберемся, что же все-таки это такое, именованный возврат?Начать стоит с того, что у нас есть стэк. При вызове функции, мы аллоцируем новый стэкфрейм функции. При возврате, мы пишем значение в стэкфрейм caller'a и делаем jmp к инструкции, на которой тот остановился. Соответственно, когда мы возвращаем во втором примере переменную result, она копируется туда, где потом будет использоваться на месте вызова. И как раз туда, куда она копируется, и указывает с!
Поэтому, в последнем примере, когда мы инкрементируем с - он уже равен result, хоть мы явно этого и не делали. Получается, мы инкрементируем значение прямо на стэкфрейме caller'a. Это, к слову, также дает нам ответ на вопрос, почему при именованном возврате из функции можно сделать просто пустой return.
А теперь самое (не)интересное: это как-то влияет на перфоманс?
Я не знаю.