Check named return error using defer function

Viewed 67

Hi I want to write a generic function to trace error message when a function returns error. So I wrote this:

func TraceError1(err *error) {
    if err != nil && *err != nil {
        pc := make([]uintptr, 15)
        n := runtime.Callers(2, pc)
        frames := runtime.CallersFrames(pc[:n])
        frame, _ := frames.Next()
        fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
    }
}

func TraceError2(err error) {
    if err != nil {
        pc := make([]uintptr, 15)
        n := runtime.Callers(2, pc)
        frames := runtime.CallersFrames(pc[:n])
        frame, _ := frames.Next()
        fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
    }
}

func foo() (err error) {
    defer TraceError1(&err)
    defer TraceError2(err)

    fmt.Println("do something")

    return fmt.Errorf("haha")
}

TraceError1 works but TraceError2 didn't. In my understanding, error is an interface so it is a pointer/address, why do I need to pass its address? Why TraceError2 cannot work? Thanks.

2 Answers

In case of TraceError1 you are passing a pointer to the named return value err. The pointer is non-nil, but the value it points at (err) is nil (at the time of defer). However, it is not yet evaluated (dereferenced) because TraceError1 has not yet been called. By the time the function does run (after foo returns) and the pointer gets dereferenced, the value of err has been updated (by the return statement inside foo).

However, in case of TraceError2, a nil interface value is passed, which will stay nil even when TraceError2 executes eventually.

Here is a simpler example:

package main

import "fmt"

func intByValue(i int) {
    fmt.Printf("i = %d\n", i)
    //                     ^--- `i` is an integer value
    //                      --- whatever i was passed to the function, gets printed
}

func intByRef(i *int) {
    var v int = *i // i is a pointer to an int, which gets dereferenced here
    //                the *address* where the actual value resides was passed
    //                while the address stays the same, its value can change before
    //                i is dereferenced, and its value stored in v.
    fmt.Printf("i = %d\n", v)
}

func main() {
    var i int

    defer intByValue(i) // passed the *value* of i, which is 0 right now
    defer intByRef(&i)  // passed a *pointer* to i, which contains 0 right now

    i = 100 // before intByRef could "dereference" its argument, the value that it
    // contained has been updated

    // intByRef gets called, dereferences the value, finds 100, prints it.
    // intByValue gets called, finds 0, prints it
    // result should be:
    //     i = 100
    //     i = 0
}

So unfortunately, if you want the ability to update the error (e.g. by returning a named return value) before it gets used by the deferred function, you are going to have to pass around pointers to the variable.

In other words, TraceError2 is simply not suited for your use case.

Edit: use correct terminology and (questionably) improve example code.

As go blog explained

The behavior of defer statements is straightforward and predictable. There are three simple rules:

  1. A deferred function's arguments are evaluated when the defer statement is evaluated.
  2. Deferred function calls are executed in Last In First Out order after the surrounding function returns.
  3. Deferred functions may read and assign to the returning function's named return values.

According to first point, when you call defer TraceError2(err) , that err = nil and that is the value pass to the TraceError2 function.

TraceError1(err *error) works because it is getting a pointer to err, and that pointer value is assigned before defer func TraceError1 is executed.

Simple example code to explain the behaviour.

package main

import (
    "fmt"
    "runtime"
)


func main() {
    i := 0
    defer func(i int) {
        fmt.Printf("%d\n",i) //Output: 0
    }(i)

    defer func(i *int) {
        defer fmt.Printf("%d\n",*i) //Output: 1
    }(&i)
    i++
}
Related