#13.2 golang defer Отложенное выполнение вызовов функций

defer ("дэфё") для отложенного выполнения

defer — это ключевое слово в Go, которое позволяет отложить выполнение функции (/вызова функции) до момента завершения выполнения текущей функции, то есть той, в теле которой defer и вызывалось.

Давайте посмотрим как работает отложенное выполнение на таком примере:

func main() {
	defer fmt.Println("После всего")
	fmt.Println("Какая-то полезная работа")
}

-- здесь "После всего" действительно выведется в консоль вторым

defer Складывает вызовы в стэк - обратный порядок выполнения

При упоминании отложенного вызова, они складываются в стэк и вызываются затем в порядке обратном тому, что указан в коде:

func main() {
	defer fmt.Println("Раз")
	defer fmt.Println("Два")
	fmt.Println("Какая-то полезная работа")
}

-- распечатает:

Какая-то полезная работа
Два
Раз

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

Несмотря на то, что сам вызов после defer выполнится после тела функции, где он вызывается, его аргументы, тем не менее, рассчитываются сразу, т.е. раньше, запустим код:

func main() {
	defer fmt.Println(myFunc())
	fmt.Println("Обычный вызов")
}

func myFunc() string {
	fmt.Println("Вычисляем аргумент")
	return "Отложенный результат"
}

-- получим:

Вычисляем аргумент
Обычный вызов
Отложенный результат

Комбинированный пример defer

С учетом более простых примеров выше, убедитесь, для кода:

func main() {
	defer fmt.Println("Давай ка после")
	defer fmt.Println(myFunc())
	fmt.Println("Обычная работа")
}

func myFunc() string {
	defer fmt.Println(myFuncNested())
	return "Отложенный результат"
}

func myFuncNested() string {
	defer fmt.Println("Отложенный вызов во вложенной функции")
	return "Вложенный отложенный результат"
}

-- что понимаете, почему распечатка будет именно в таком порядке:

Отложенный вызов во вложенной функции
Вложенный отложенный результат
Обычная работа
Отложенный результат
Давай ка после

-- помним, что на порядок влияют следующие правила:

  • отложенные вызовы складываются в стэк
  • аргументы для отложенных вызовов вычисляются сразу (неотложно, а не отложенно ;)

- основную запутанность, тут вызывает именно момент с неотложным вычислении агрументов для отложенной функции. Поэтому, если мы хотим, что всё что относится к отложенной функции выполнялось "после", нам нужно:

  • либо отказаться от вычисления аргументов "на ходу"
  • либо завернуть само это вычисление в анонимную функцию и вызвать её без аргментов, например:
    func main() {
    	defer fmt.Println("Просто отложенное")
    	defer func() {
    		fmt.Println(myFunc())
    	}()
    	fmt.Println("Обычная работа")
    }
    
    func myFunc() string {
    	fmt.Println("Тело отложенной функции")
    	return "Отложеный результат"
    }
    

    -- здесь мы получим распечатку:

    Обычная работа
    Тело отложенной функции
    Отложеный результат
    Просто отложенное
    

    -- теперь аргумент для fmt.Println() вычисляется отложенно, а не сразу (т.к. теперь он обернут вызываемой отложенно анонимной функцией)

Когда полезно использовать defer

Чаще всего этот подход используется, когда:

  • нужно посчитать, например, время работы функции
  • закрыть какой-то ресурс: например, сетевое соединение или файловый дескриптор.

-- короче говоря, как-то подытожить работу текущей функции.

Что еще почитать

Как использовать defer в Go: https://habr.com/ru/companies/otus/artic...