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

Ответ

Отложенные функции в Go, объявленные с помощью ключевого слова defer, выполняются в обратном порядке их объявления — по принципу LIFO (Last-In, First-Out). Последняя объявленная defer функция выполнится первой.

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

Пример порядка выполнения:

package main

import "fmt"

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

Вывод:

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

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

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

    func printArgument() {
        i := 0
        defer fmt.Println("Значение i:", i) // i = 0
        i++
        // Вывод будет "Значение i: 0", так как i было вычислено в строке с defer
    }
  2. Гарантированное выполнение: defer полезен для освобождения ресурсов (например, закрытие файлов file.Close() или разблокировка мьютексов mu.Unlock()), так как он будет выполнен даже если в функции произойдет паника.

Ответ 18+ 🔞

А, ну вот, смотри, про эти ваши defer в Go. Говорят, штука полезная, а на деле — как обычно, подвох есть, блядь. Всё просто, как три копейки, но мозг сломать можно, если не врубиться сразу.

Представь, ты в баре. Говоришь бармену: «Слушай, мужик, я вот сейчас выпью, но ты мне потом, когда я уже буду уходить, напомни, что я должен был заплатить, закрыть за собой дверь и ещё послать бывшую нахуй». Это и есть defer. Ты заказываешь действие, но выполнится оно потом, когда твоя пьяная тушка будет валить из функции, то есть из бара.

И самое главное — порядок, ёпта! Ты же не будешь сначала бывшую слать, а потом платить? Нет. Ты делаешь всё наоборот. Кто последний заказал — тот первый и получит. LIFO, блядь, как в стеке посуды грязной. Последнюю тарелку сверху помыть проще всего.

Вот, смотри на код, тут всё ясно:

package main

import "fmt"

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

Вывод будет предсказуем, если не охуеть от простоты:

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

Видишь? Сначала выполнилось всё обычное, а потом пошла раздача отложенных долгов, но задом наперёд. Первым выполнился тот defer, который объявили последним. Логично? Вроде да, но мозг всё равно чешется.

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

func printArgument() {
    i := 0
    defer fmt.Println("Значение i:", i) // i = 0, запомнили!
    i++ // Ой, а тут i уже стало 1!
    // Вывод будет "Значение i: 0", потому что в момент defer i был ноль, ёбта!
}

Вот это уже весело, да? Кажется, что должно вывести 1, а выводит 0. Потому что Go, сука, хитрый — он как бы «фотографирует» значение i в тот самый момент, когда видит строку с defer. А потом, когда дело доходит до выполнения, он достаёт старую фотографию и показывает её. Вот такая хитрая жопа.

И последнее, самое важное. defer — это твой бронежилет. Он выполнится в 99.9% случаев. Функция завершается нормально? Выполнится. Функция наткнулась на panic и пошла в разнос? Всё равно выполнится, прямо перед тем как всё окончательно накроется медным тазом. Поэтому его и используют для всякой обязательной финализации: закрыть файл, отпустить мьютекс, отправить сигнал «мама, я жив». Чтобы не получилось, как в том анекдоте — «ушел, забыл выключить утюг и спалил всю квартиру». С defer утюг выключится гарантированно, даже если тебя во время глажки хватит инфаркт.

Короче, штука мощная, но с характером. Используй, но головой думай, что и когда ты туда засовываешь.