Что такое `defer` в Go и каковы его ключевые особенности?

Ответ

Ключевое слово defer в Go используется для отложенного выполнения вызова функции до тех пор, пока содержащая её функция не завершит свое выполнение. Основное предназначение defer — это управление ресурсами, такими как закрытие файлов, разблокировка мьютексов или закрытие сетевых соединений.

Пример с закрытием файла:

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    // file.Close() будет вызван непосредственно перед выходом из processFile,
    // независимо от того, как функция завершится (через return или панику).
    defer file.Close()

    // ... код для работы с файлом ...
    return nil
}

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

  1. Порядок выполнения LIFO (Last-In, First-Out): Если в функции есть несколько defer вызовов, они будут выполнены в порядке, обратном их объявлению. Последний объявленный defer выполнится первым.

    func lifoExample() {
        defer fmt.Println("третий")
        defer fmt.Println("второй")
        defer fmt.Println("первый")
    } 
    // Вывод будет:
    // первый
    // второй
    // третий
  2. Вычисление аргументов в момент вызова: Аргументы отложенной функции вычисляются в тот момент, когда компилятор встречает оператор defer, а не когда функция фактически выполняется.

  3. Гарантированное выполнение: Отложенный вызов гарантированно выполнится, даже если в функции произойдет panic. Это делает defer идеальным инструментом для очистки ресурсов.

  4. Взаимодействие с именованными результатами: defer может читать и изменять именованные возвращаемые значения функции. Это полезно для обработки ошибок или модификации результата перед возвратом.

    func getNumber() (result int) {
        defer func() {
            // Этот код выполнится после 'return 10',
            // но до фактического возврата значения из функции.
            result *= 2
        }()
        return 10
    }
    // getNumber() вернет 20

Ответ 18+ 🔞

Смотри, вот эта штука defer в Go — это вообще пиздец какой удобный инструмент, если понять, как он работает. По сути, это твой личный уборщик, который приходит после твоего бардака и всё за тобой закрывает, выключает и подтирает. Чтобы ты не накосячил и не забыл, как последний распиздяй.

Представь, ты открываешь файл, чтобы почитать. В нормальном, не Go-шном мире, ты бы читал-читал, а потом охуел бы, потому что забыл его закрыть, и у тебя бы всё повисло. А тут ты просто говоришь: «Эй, Go, слушай сюда, как только я тут всё сделаю, неважно, как я закончу — нормально или с криком и паникой — ты первым делом закрой этот файл, блядь». И он закрывает. Чисто, аккуратно, без лишних движений.

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err // Если ошибка, вылетаем отсюда
    }
    // А вот и магия. Сказали — и забыли. Закроется само.
    defer file.Close()

    // ... делаем тут что хотим с файлом, хуярим данные ...
    return nil // И даже тут, на выходе, file.Close() вызовется!
}

А теперь про тонкости, а то народ нихуя не понимает и потом охуевает.

Первое — порядок. Это как тарелки на кухне после застолья. Кто последний поставил — тот сверху. С defer так же: кто последний объявлен — тот первый и выполнится. LIFO, блядь, Last-In, First-Out.

func lifoExample() {
    defer fmt.Println("третий")
    defer fmt.Println("второй")
    defer fmt.Println("первый")
}
// Выведет: первый, второй, третий. Снизу вверх, как будто стопку снимаешь.

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

Третье — гарантии. Это самое офигенное. Даже если твоя функция накроется медным тазом, взвоет и вызовет panic, все defer'ы всё равно отработают. Это как бронежилет для твоих ресурсов. Файл закроется, мьютекс разлочится, соединение прикроется. Красота.

Четвёртое — хитрость с возвращаемыми значениями. Если у тебя функция возвращает именованное значение, то defer может его подкрутить в последний момент, прямо перед самым вылетом. Это как сделать финальный штрих уже после того, как вроде всё готово.

func getNumber() (result int) {
    defer func() {
        // Выполнится ПОСЛЕ 'return 10', но ДО того, как значение улетит наружу.
        result *= 2 // Подменяем результат на лету!
    }()
    return 10 // Кажется, вернём 10? Хуй там! Вернём 20.
}
// getNumber() вернёт 20. Вот такая магия, блядь.

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