DEV Community

maxwellhertz
maxwellhertz

Posted on

Something fun about `defer`

#go

I read this in Effective Go:

The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes. Besides avoiding worries about variables changing values as the function executes, this means that a single deferred call site can defer multiple function executions.

At first I got confused: what does that mean by saying "The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes." ? I continued reading this passage and I found this example:

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

// output:
// entering: b
// in b
// entering: a
// in a
// leaving: a
// leaving: b
Enter fullscreen mode Exit fullscreen mode

OMG I got more confused. WHY was "entering" printed before "in" ??? I read the passage again and again and finally I thought I revealed what happened under the hood.

defer will acutally postpone the most outer function if we try to defer multiple functioin executions. In this case, the most outer function is un which needs a string parameter so trace will be executed to evaluate this parameter. In other words, the deferred function's parameters will be evaluaed when it is deferred. Therefore, "entering" is printed before "in".

Let's try a more complicated example and I'm pretty sure you know the drill:

func main() {
    b()
}

func entering(s string) string{
    fmt.Println("entering:", s)
    return s
}

func entered(s string) string {
    fmt.Println("entered:", s)
    return s
}

func leaving(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer leaving(entered(entering("a")))
    fmt.Println("in a")
}

func b() {
    defer leaving(entered(entering("b")))
    fmt.Println("in b")
    a()
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)