Когда функция defer оценивает свои параметры

Я изучаю поведение defer в golang и хочу использовать его для обработки ошибок при возврате функции.

Код выглядит следующим образом:

package main

import "fmt"
import "errors"

func main() {
    a()
}

func a() {
    var err error   
    defer func(){
        if err != nil {
            fmt.Printf("1st defer: %s\n", err)
        } else {
            fmt.Println("1st defer: defer not error")
        }
    }()
    defer func(err error){
        if err != nil {
            fmt.Printf("2nd defer: %s\n", err)
        } else {
            fmt.Println("2nd defer: defer not error")
        }
    }(err)

    err = errors.New("new error")
    if err != nil {
        return
    }
}

Выход:

2nd defer: defer not error
1st defer: new error

В Doc говорится, что параметры оцениваются при оценке вызова отсрочки, что, по-видимому, должно быть последовательный. Почему 2 defer имеет другое значение для переменной err и, следовательно, другой вывод? Я знаю, что это связано со второй функцией, которая имеет err в качестве входного параметра, но не знаю почему.


person Yulong    schedule 09.03.2017    source источник


Ответы (3)


Хорошо, я только что понял. Если вы передаете какие-либо параметры функции отсрочки (например, 2-я функция отсрочки выше), эти параметры оцениваются, когда функция отложена, а не когда они выполняются. Это означает, что в моем примере err все еще nil и еще не назначено новой ошибке.

с другой стороны, в 1-м отложенном выше err является не параметром, а переменной в функции a, и когда 1-й отложенный выполняется, он уже был присвоен новой ошибке.

person Yulong    schedule 09.03.2017

Другой способ — использовать ссылку на исходную переменную err.

package main

import (
    "errors"
    "fmt"
)

func main() {
    a()
}

func a() {
    var err error
    defer func() {
        if err != nil {
            fmt.Printf("1st defer: %s\n", err)
        } else {
            fmt.Println("1st defer: defer not error")
        }
    }()
    defer func(err *error) {
        if *err != nil {
            fmt.Printf("2nd defer: %s\n", *err)
        } else {
            fmt.Println("2nd defer: defer not error")
        }
    }(&err)

    err = errors.New("new error")
    if err != nil {
        return
    }
}

И вывод:

2nd defer: new error
1st defer: new error
person HubertS    schedule 29.08.2018
comment
Это было очень полезно. Я не мог заставить свою функцию отката транзакции базы данных работать, так как ей передавалась ошибка в отложенной функции, но как только я начал передавать ей указатель ошибки, она сработала как шарм. - person Slotheroo; 17.11.2018

Есть еще одна аналогичная ситуация в случае Defer Statement и Defer Function. Пожалуйста, посмотрите на пример ниже

package main

import (
    "fmt"
    "time"
)

func main() {

    start := time.Now()
    time.Sleep(3*time.Second)
    defer func() { fmt.Println("Defer Function Elapsed Time: ", time.Since(start)) }() //Defer Function
    defer fmt.Println("Defer Statement Elapsed Time: ", time.Since(start)) //Defer Statement
    time.Sleep(3*time.Second)
}

Вывод:

Прошедшее время отсрочки заявления: 3 с

Прошедшее время функции отсрочки: 6 с

Попробуйте выше в идите играть

Это связано с тем, что в Deferred Statement случае аргументы отложенного вызова оцениваются немедленно см. документ

person arunjos007    schedule 02.05.2019