Каков жизненный цикл и порядок выполнения отложенных вызовов (defer) в Go?

Ответ

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

Ключевые особенности defer:

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

    func main() {
        i := 1
        defer fmt.Println("Значение i:", i) // Захватит значение i = 1
        i++
        fmt.Println("Текущее значение i:", i)
    }
    // Вывод:
    // Текущее значение i: 2
    // Значение i: 1
  2. Порядок выполнения LIFO (Last-In, First-Out): Если в функции несколько defer, они выполняются в порядке, обратном их объявлению. Последний объявленный defer выполнится первым.

    func main() {
        fmt.Println("Начало")
        defer fmt.Println("Первый defer")
        defer fmt.Println("Второй defer")
        fmt.Println("Конец")
    }
    // Вывод:
    // Начало
    // Конец
    // Второй defer
    // Первый defer
  3. Основное применение: defer идеально подходит для задач очистки ресурсов, таких как закрытие файлов, разблокировка мьютексов или закрытие сетевых соединений, гарантируя их выполнение вне зависимости от того, как функция завершается.

    func processFile(filename string) error {
        f, err := os.Open(filename)
        if err != nil {
            return err
        }
        defer f.Close() // Гарантированное закрытие файла
    
        // ... работа с файлом ...
        return nil
    }