Ответ
Отложенные функции в 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:
-
Немедленное вычисление аргументов: Аргументы для отложенной функции вычисляются в момент объявления
defer, а не в момент её выполнения.func printArgument() { i := 0 defer fmt.Println("Значение i:", i) // i = 0 i++ // Вывод будет "Значение i: 0", так как i было вычислено в строке с defer } -
Гарантированное выполнение:
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 утюг выключится гарантированно, даже если тебя во время глажки хватит инфаркт.
Короче, штука мощная, но с характером. Используй, но головой думай, что и когда ты туда засовываешь.