Материал проходит вычитку — возможны неточности.
Урок 53 из 118

Аргументы defer и именованный результат

Замер времени в логах «Полки» показывал почти ноль, а в отчёт уходили старые данные. Миша нашёл причину: уборка захватывала значения в момент объявления, до того как они изменились.

Главная мысль

аргументы отложенного вызова вычисляются в момент объявления defer, а замыкание видит переменную живой — и может изменить именованный результат.

Чаще всего людей сбивают с толку две вещи: порядок выполнения (о нём — прошлый урок) и момент вычисления аргументов.

Аргументы вычисляются сразу

Go

fmt.Println(i) увидел i равной нулю и именно ноль напечатает на выходе, хотя к тому моменту переменная уже единица. Ловит это людей в реалистичных сценариях, самый частый из которых — замер времени:

t := time.Now()
defer log.Println(time.Since(t)) // неправильно: аргумент посчитается сразу

Кажется, что time.Since(t) посчитается на выходе. На деле аргумент вычисляется при defer, поэтому в лог уйдёт почти ноль. Именно так замер длительности операции в «Полке» писал в лог бессмысленные нули. Правильно — обернуть в замыкание:

defer func() { log.Println(time.Since(t)) }()

Замыкание видит живую переменную

Go

Тело замыкания захватывает переменную i и читает её в момент выполнения, уже после присваивания. Противоречия с примером выше нет: defer fmt.Println(i) скопировал значение аргумента, а замыкание работает с живой переменной.

Именованный результат

defer может менять возвращаемое значение — но только именованное:

func count() (n int) {
    defer func() { n++ }()
    return 2
}

fmt.Println(count()) // 3

return 2 присваивает n = 2, потом выполняются отложенные вызовы, один из них делает n++, и уже после этого функция отдаёт наружу итоговое n = 3. С анонимным результатом отложенной функции не за что было бы зацепиться.

Та же логика у получателя метода: defer c.log снимает копию c в момент объявления, и метод с value-receiver отработает именно с ней.

В следующем уроке

Вы узнаете:

  • почему recover живёт именно в defer
  • какие накладные расходы у defer в горячем цикле
Задание · вопросы

Проверьте себя

Вопрос 1 из 3

i := 0; defer fmt.Println(i); i = 1. Что напечатается?

Вопрос 2 из 3

defer c.log() с value-receiver, затем c.n = 2. Что напечатает log?

Вопрос 3 из 3

Почему defer log.Println(time.Since(t)) пишет в лог почти ноль?

Итоги

Аргументы defer фиксируются сразу; замыкание видит переменную в момент выполнения; именованный результат defer может подправить. Три правила, на которых держится почти вся путаница с defer.

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки