softshipper
softshipper

Reputation: 34071

how defer statement works exactly

i try to learn golang and use effective go as lecture. I stuck on capital defer, look at the following code

package main

import "fmt"

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()
}

as output i've got

entering: b
in b
entering: a
in a
leaving: a
leaving: b

I know, that defer statement will be execute after return statement in the function. But here, why entering: b is the first output? I am expected in b as the fist output!

Upvotes: 2

Views: 581

Answers (2)

RoninDev
RoninDev

Reputation: 5666

According to http://golang.org/doc/effective_go.html#defer:

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.

So argument of defer function (trace("b") in your case) evaluates first

Upvotes: 8

peterSO
peterSO

Reputation: 166569

Simplifying,

package main

import "fmt"

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

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

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

func main() {
    b()
}

Output:

entering: b
in b
leaving: b

Since "arguments to deferred functions are evaluated when the defer executes", the

defer un(trace("b"))

statement in function b is, using explicit scope, equivalent to

{
    unarg := trace("b")
    defer un(unarg)
}

Therefore, equivalently,

package main

import "fmt"

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

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

func b() {
    {
        unarg := trace("b")
        defer un(unarg)
    }
    fmt.Println("in b")
}

func main() {
    b()
}

Output:

entering: b
in b
leaving: b

Equivalently, for your complete example,

package main

import "fmt"

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

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

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

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

func main() {
    b()
}

Output:

entering: b
in b
entering: a
in a
leaving: a
leaving: b

Upvotes: 2

Related Questions