Detailed usage rules of defer in golang

  • 2020-06-03 06:54:35
  • OfStack

preface

In golang, the defer block adds a function call to the list of function calls. This function call is not a normal function call, but adds a function call after the normal return of the function, return. Therefore, defer is often used to release variables inside functions.

To learn more about defer's behavior, let's start with the following code:


func CopyFile(dstName, srcName string) (written int64, err error) { 
src, err := os.Open(srcName) 
if err != nil { 
return 
}

dst, err := os.Create(dstName) 
if err != nil { 
return 
}

written, err = io.Copy(dst, src) 
dst.Close() 
src.Close() 
return 
}

This code works, but there are 'security concerns'. If the call dst, err := os.Create(dstName) On failure, the function executes return to exit the run. But the src(file handle) created earlier is not released. The above code is simple, so we can see that there is a problem with files not being released. If our logic is complex or our code calls too much, this error may not be detected in time. This can be avoided by using defer. Here is the code to use defer:


func CopyFile(dstName, srcName string) (written int64, err error) { 
src, err := os.Open(srcName) 
if err != nil { 
return 
}
defer src.Close()

dst, err := os.Create(dstName) 
if err != nil { 
return 
}
defer dst.Close()

return io.Copy(dst, src) 
}

With defer, we can gracefully close/clean up the variables used in the code. defer, as the property of golang cleanup variables, has its own unique and explicit behavior. Here are the defer3 rules of use.

Rule 1 When defer is declared, its parameters are resolved in real time

We explain this rule in the following code:


func a() { 
i := 0 
defer fmt.Println(i) 
i++ 
return 
}

As we said above, the defer function is called after return. So after this function executes, shouldn't it output 1?

The result is 0.why?

This is because although we define a function with a variable after defer: fmt.Println(i) But this variable (i) has already been determined by the time defer is declared. In other words, the code above is the same as the code below:


func a() { 
i := 0 
defer fmt.Println(0) // because i=0 So I'm going to tell you explicitly right now golang When the program exits, the output is executed 0 The operation of the  
i++ 
return 
}

To make this more clear, we continue to define an defer:


func a() { 
i := 0 
defer fmt.Println(i) // The output 0 Because the i At this point is 0 
i++ 
defer fmt.Println(i) // The output 1 Because the i At this point is 1 
return 
}

By running the results, you can see that the value of the defer output is the value at the time of definition. Not the value of defer when it is actually executed (very important, if you don't understand it, it will produce the expected result).

But why output 1 first and then output 0? Look at rule 2 below.

Rule 2 defer is executed in order of "in" and "out"

When multiple defer blocks are defined at the same time, the golang installation calls defer in the sequence of definition followed by execution. No, that's how golang is defined. We use the following code to deepen our memory and understanding:


func b() { 
for i := 0; i < 4; i++ { 
defer fmt.Print(i) 
}
}

In the loop, four defer blocks are defined in turn. Combined with rule 1, we know exactly what value each defer block should output. Install in and out principle, we can see the output 3210 in turn.

Rule 3 defer can read named return values

Take a look at the following code:


func c() (i int) { 
defer func() { i++ }() 
return 1 
}

The output is 12. In the beginning, we said that defer is executed after the return call. It should be clear that the scope of the defer block is still within the function. In combination with the above function, the scope of defer is still within the c function. So defer can still read the variables in the c function (if you can't read the variables in the function, how do you clear the variables...) .

When return 1 is executed, the value of i is 1. At this point, the defer block begins to execute and the i is incrementing. So output 2.

By mastering the above three rules of defer, we can clearly know the expected results of defer when we encounter the defer code block.

conclusion


Related articles: