Golang’s defer execution rule description

Time:2021-12-1

Introduction to defer

Defer is a feature of golang, which is called “deferred call function”. Execute defer when the external function returns. It is similar to try… Catch… Finally… In other languages. Of course, the difference is obvious.

Before using defer, we should know more about the features of defer, so as to avoid misunderstandings in use.

1. The simplest defer

func test(){
    defer func(){ fmt.Println("defer") }()
    //todo
    //...
    return
    //Defer execution timing
}

We can change the above code slightly to confirm the execution time of defer again.

func main() {
    fmt.Println(test())
}
func test() (i int) {
    defer func() { i++ }()
    defer func() { fmt.Println(i) }()
    //todo
    //...
    fmt.Println(0)
    return 1
    //Defer execution timing
}

output:

0

1

2

From the above example, it can be found that defer is executed after return and in the order of first in and last out declared by defer. The following are common usage in real scenes.

Release occupied resources

func test() error {
    file, err := os.Open("path")
    if err != nil {
        return err
    }
    //After judging err status
    defer file.Close()
    //todo
    //...
    return nil
    //Defer execution timing
}

Capture and handle exceptions

func test2() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    file, err := os.Open("path")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    //todo
    //...
    return
    //Defer execution timing
}

Closing work such as output log

func test3() {
    t1 := time.Now()
    defer func() {
        FMT. Printf ("time consuming:% f s", time. Now(). Sub (T1). Seconds())
    }()
    //todo
    //...
    return
    //Defer execution timing
}

2. Complex defer

When we have deeply remembered the execution timing of defer and intend to turn this page, the development of things began to deviate from the original. Please see the following code

func test() {
    i := 0
    Defer FMT. Println (I) // output 0
    Defer func (x int) {FMT. Println (x)} (I) // output 0
    Defer func (x * int) {FMT. Println (* x)} // output 1
    Defer func() {FMT. Println (I)}() // output 1
    i++
    //todo
    //...
    FMT. Println (I) // output 1
    return   
}

output:

1

1

1

0 / / the value is not modified

0 / / the value is not modified

It is often thought that defer is really moved after return.

But the essence of defer is still function call. When the defer definition is executed, the parameter will be evaluated first, and then the parameter will be pushed into the function call stack. At this time, it will not enter the defer function body, but will not call the defer function body until the function returns.

When the parameter is pushed into the function call stack, if the parameter is a value type, the value will be copied. If the parameter is a pointer, the pointer will be copied instead of the value pointed to by the pointer.

The variables in the defer function are executed after return, so they are not affected.

Therefore, when using defer, we must specify the parameter type of the function (if any). Secondly, we must specify whether the variable reference in the defer function is correct.

The following are common errors

func test4() error {
    f, err := os.Open("A.txt")
    if err != nil {
        return err
    }
    Defer func() {f.close()}() // error: closing is a B file, and the f reference is reassigned
    f, err = os.Open("B.txt")
    if err != nil {
        return err
    }
    Defer func() {f.close()}() // close the B file
    list := []int{1, 2}
    for _, i := range list {
        Defer FMT. Println (I) // output 2 1 // I is the value type, and the parameter is copied
        Defer func() {FMT. Println (I)}() // error: output 2 // reference I in the function body and leave the final value
    }
    return nil
}

3. More complex defer

Let’s look at the following code

type Test struct {
    name string
}
Func (this * test) point() {// this is a pointer
    fmt.Println(this.name)
}
Func (this test) value() {// this is the value type 
    fmt.Println(this.name)
}
func test5() {
    ts := []Test{{"a"}, {"b"}, {"c"}}
    for _, t := range ts {
        Defer t.point() // output C
        Defer t.value() // output C B a
    }
}

Seemingly the same code outputs completely different results. To understand this difference, we have to speak from the essence of the calling function. The method call of golang to struct is like this


defer func (this Type, para) result

The value type or pointer used by this in the definition of struct method determines the replication of the first parameter (hidden) when defer is called.

In the above code, the pointer is used as this when defining the point method, so the output is the T reference of the final assignment of the for loop.

The value method uses the value type as this when it is defined, so the output is the T after each step of the copy executed by the for loop.

Finally, only by understanding the above problems can the troubles caused by this defer be far away from us.

Supplement: three practical points of defer in golang

The defer in golang is used frequently and can create a way to delay the effectiveness of special effects.

Defer also has its own affectation, which needs attention.

This article will explain the three hypocrisy of defer through code.

1. Effective order of defer

Let’s start with the conclusion: defer is executed in reverse order (same as stack in first out)

func main() {
 defer func() {
  Fmt.println ("I'll come out later")
 }()
 defer func() {
  Fmt.println ("I'll come out first")
 }()
}

Print out after execution:

I’ll come out first

I’ll come out later

2. Defer and return, the order between the return values of the function

Let’s start with the conclusion: return executes first – > return is responsible for writing the result into the return value – > then defer starts to perform some finishing work – > finally, the function exits with the current return value

We know that there are two ways to express the return value according to whether it is declared in advance: one is func test() int, and the other is func test() (I int), so let’s talk about both cases

func test() int
func main() {
 fmt.Println("main:", test())
}
func test() int {
 var i int
 defer func() {
  i++
  FMT. Println ("value of defer2:", I)
 }()
 defer func() {
  i++
  FMT. Println ("value of defer1:", I)
 }()
 return i
}

Output:

Value of defer1: 1

Value of defer2: 2

main: 0

Detailed explanation: the return value is defined as 0 when returning. Since I is declared inside the function, even if the + + operation is performed in defer, it will not affect the decision made when returning.

func test() (i int)
func main() {
 fmt.Println("main:", test())
}
func test() (i int) {
 defer func() {
  i++
  FMT. Println ("value of defer2:", I)
 }()
 defer func() {
  i++
  FMT. Println ("value of defer1:", I)
 }()
 return i
}

Output:

Value of defer1: 1

Value of defer2: 2

main: 2

Detailed explanation: since the return value is declared in advance, the return value determined at the time of return is still 0. However, after the execution of the latter two defers, the + + is performed twice to change the value of I to 2. After the execution of defers, the function returns the value of I.

3. Defer defines and executes two steps to do things

Let’s talk about the conclusion first: the value (or address) of the parameter part of the function after defer will be given first [you can understand that the value in () will be determined first]. After the function is executed, the logic in {} of the function after defer will be executed

func test(i *int) int {
 return *i
}
func main(){
 var i = 1
 //When defer is defined, the value of test (& I) is set to 1, and it will not change later
 defer fmt.Println("i1 ="  , test(&i))
 i++
 //When defer is defined, the value of test (& I) has been set to 2, and it will not change later
 defer fmt.Println("i2 ="  , test(&i))
 //When defer is defined, I has determined that it is a pointer type. The value on the address changes, and here it changes
 defer func(i *int) {
  fmt.Println("i3 ="  , *i)
 }(&i)
 //When defer is defined, the value of I has been set to 2, and it will not change later
 defer func(i int) {
  //The defer is defined at the time of definition
  fmt.Println("i4 ="  , i)
 }(i)
 defer func() {
  //Address, so it will change later
  var c = &i
  fmt.Println("i5 ="  , *c)
 }()
 
 //It is called only after I = 11 is executed. At this time, the I value is already 11
 defer func() {
  fmt.Println("i6 ="  , i)
 }()
 i = 11
}

The above is my personal experience. I hope I can give you a reference, and I hope you can support developpaer. If you have any mistakes or don’t consider completely, please don’t hesitate to comment.