Какой порядок выполнения нескольких операторов `defer` в одной функции?

Ответ

Операторы defer в Go выполняются в порядке LIFO (Last-In, First-Out) — «последним пришел, первым вышел». Это означает, что самый последний defer в коде функции выполнится первым, а самый первый — последним.

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

Простой пример

package main

import "fmt"

func main() {
    fmt.Println("Начало функции")
    defer fmt.Println("Выполнится последним (1)")
    defer fmt.Println("Выполнится вторым (2)")
    defer fmt.Println("Выполнится первым (3)")
    fmt.Println("Конец функции")
}

Вывод:

Начало функции
Конец функции
Выполнится первым (3)
Выполнится вторым (2)
Выполнится последним (1)

Важные нюансы

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

    func printArg() {
        i := 0
        defer fmt.Println("Значение i:", i) // i здесь равно 0
        i++
        fmt.Println("Функция завершается")
    }
    // Вывод:
    // Функция завершается
    // Значение i: 0
  2. Взаимодействие с именованными возвращаемыми значениями: defer может читать и изменять именованные возвращаемые значения функции. Это полезно для обработки ошибок или модификации результата перед возвратом.

    func getNumber() (n int) { // n - именованное возвращаемое значение
        defer func() {
            n = n * 2 // Модифицируем n перед возвратом
        }()
        return 5 // 1. n присваивается 5, 2. выполняется defer, 3. возвращается n
    }
    // getNumber() вернет 10
  3. Основное применение: defer идеально подходит для освобождения ресурсов, таких как закрытие файлов, разблокировка мьютексов или закрытие сетевых соединений, гарантируя их выполнение независимо от того, как завершилась функция (успешно или с паникой).