Как в отложенном вызове (defer) получить доступ к возвращаемым значениям функции?

Ответ

Чтобы получить доступ к результатам выполнения функции внутри defer, необходимо использовать именованные возвращаемые значения в сочетании с анонимной функцией (замыканием).

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

Способ 1: Именованные возвращаемые значения (предпочтительный)

Если вы объявляете переменные для возвращаемых значений в сигнатуре функции, они становятся доступны внутри всей функции, включая отложенные замыкания.

func example() (result int) { // 'result' — именованное возвращаемое значение
    // Анонимная функция в defer захватывает 'result' из своей области видимости.
    defer func() {
        // Этот код выполнится перед выходом из функции,
        // когда 'result' уже будет присвоено значение 42.
        fmt.Printf("Deferred call. Result is: %dn", result)
        // Здесь можно даже изменить возвращаемое значение!
        result++
    }()

    fmt.Println("Assigning result...")
    return 42
}

// Вызов `example()` вернет 43, а не 42.

Способ 2: Использование указателя

Если вы не хотите использовать именованные возвращаемые значения, вы можете передать в defer указатель на переменную с результатом.

func anotherExample() int {
    var result int
    // Передаем указатель на 'result' в отложенную функцию
    defer func(res *int) {
        fmt.Printf("Deferred call. Result is: %dn", *res)
    }(&result)

    result = 100
    return result
}

Распространенная ошибка

Попытка передать само значение, а не использовать замыкание или указатель, приведет к тому, что в defer будет передано значение переменной на момент объявления defer.

func wrongExample() int {
    var result int
    // ОШИБКА: fmt.Println получит значение 'result' (равное 0) в ЭТОТ момент.
    defer fmt.Println("Result:", result)

    result = 42 // Это изменение не повлияет на значение, переданное в defer
    return result
}
// Выведет: "Result: 0"

Ответ 18+ 🔞

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

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

Способ 1: Именованные возвращаемые значения (нормальный способ)

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

func example() (result int) { // Вот, 'result' — это именованная переменная для возврата
    defer func() {
        // Эта штука выполнится прямо перед тем, как функция ебётся на выход.
        // К этому моменту в 'result' уже будет лежать 42.
        fmt.Printf("Смотри-ка, а результат-то уже: %dn", result)
        // А вот тут можно нагло его подменить, блядь!
        result++ // Теперь функция вернёт 43, а не 42. Вот такой пиздец.
    }()

    fmt.Println("Присваиваю результат...")
    return 42 // Кажется, что вернём 42, но defer всё испортит... или улучшит.
}
// Вызов `example()` вернёт 43. Вот тебе и сюрприз, ебать мои старые костыли.

Способ 2: Через указатель (если не любишь именованные возвраты)

Ну а если тебе именованные возвраты как кость в горле, можно тупо передать указатель на свою переменную.

func anotherExample() int {
    var result int
    // Кидаем в defer указатель на 'result'
    defer func(res *int) {
        fmt.Printf("А в defer'е результат уже: %dn", *res)
    }(&result)

    result = 100
    return result
}

Типичная ошибка, от которой все охуевают

А вот если попробовать по-простому, без замыкания, то получится пиздец и разочарование.

func wrongExample() int {
    var result int
    // ОШИБКА: fmt.Println схватит значение 'result' СЕЙЧАС. А сейчас оно равно 0, ёпта!
    defer fmt.Println("Результат:", result)

    result = 42 // Это присваивание defer'у уже похуй, он своё значение давно зафиксировал.
    return result
}
// Напечатает: "Результат: 0". Вот и весь сказ, блядь. Чих-пых тебя в сраку.

Короче, запомни: хочешь в defer поиграть с результатом — либо именованные возвраты с замыканием, либо указатель. Иначе будешь как тот Герасим, только "Му-му" говорить и не мочь нихуя исправить.