Preface
First statement: I have been cheated.
When I was writing a Go column before, I wrote an article:Go column | Error handling: defer, panic and recover. A friend left a message saying: I understand the truth, but I still don’t know how to use it, and there are always inexplicable problems.
It's right if something goes wrong. This little thing is very bad and makes mistakes if you are not careful.
So, in the face of this situation, we are unreasonable today. I just took the code I had collected for many years, and with my years of experience in stumbled on pits and writing bugs, I would stand and take this pit over.
1. Simple examples
Let’s first give a simple example to warm up:
package main import ( "fmt" ) func main() { defer func() { ("first") }() defer func() { ("second") }() ("done") }
Output:
done
second
first
This is relatively simple. The execution order of the defer statement is executed in reverse order of the call defer statement.
2. Do not use defer statements in the for loop
See what's wrong with this code?
for _, filename := range filenames { f, err := (filename) if err != nil { return err } defer () }
This code is actually very dangerous and will likely use up all file descriptors. Because the defer statement will not be executed until the last moment of the function, which means that the file cannot be closed at all. So remember not to use the defer statement in the for loop.
So how to optimize it? You can write a function separately to the loop body, so that the closing function will be called every time the loop is looped.
as follows:
for _, filename := range filenames { if err := doFile(filename); err != nil { return err } } func doFile(filename string) error { f, err := (filename) if err != nil { return err } defer () }
3. Do not use naming to return values when defining functions
Let’s see what the output results of these three functions are?
package main import ( "fmt" ) func a() (r int) { defer func() { r++ }() return 0 } func b() (r int) { t := 5 defer func() { t = t + 5 }() return t } func c() (r int) { defer func(r int) { r = r + 5 }(r) return 1 } func main() { ("a = ", a()) ("b = ", b()) ("c = ", c()) }
Announced answer:
a = 1 b = 5 c = 1
Have you answered correctly?
To be honest, when I first saw this result, I was quite puzzled and had no idea what was going on.
But we can see that these three functions have a common feature, they all have a named return value, and they all refer to this return value in the function.
There are two ways to quote: closure and function parameters.
Let’s look at the a() function first:
The closure modified the external variable through r++, and the return value became 1.
Equivalent to:
func aa() (r int) { r = 0 // Before return, execute the defer function func() { r++ }() return }
Let’s look at the b() function again:
The local variable t is modified in the closure, while the external variable t is not affected, so 5 is still returned.
Equivalent to:
func bb() (r int) { t := 5 // Assignment r = t // Before return, execute the defer function // The defer function did not modify the return value r, but only modified the variable t func() { t = t + 5 }() return }
Finally, the c function:
Parameter passing is a value copy, and the actual parameter is not affected, so it still returns 1.
Equivalent to:
func cc() (r int) { // Assignment r = 1 // The r modified here is the value of the function parameter // Copy the value and do not affect the actual parameter value func(r int) { r = r + 5 }(r) return }
So, in order to avoid writing such unexpected code, it is best not to use named return values when defining functions. Or if used, do not quote it in defer.
Let’s look at the following two examples:
func d() int { r := 0 defer func() { r++ }() return r } func e() int { r := 0 defer func(i int) { i++ }(r) return 0 }
d = 0
e = 0
The return value meets expectations, and you no longer have to rack your brains to guess.
4. If the function of the defer expression is behind panic, this function cannot be executed.
func main() { panic("a") defer func() { ("b") }() }
The output is as follows, b is not printed.
panic: a goroutine 1 [running]: () :87 +0x4ce exit status 2
And if defer is in front, it can be executed.
func main() { defer func() { ("b") }() panic("a") }
Output:
b panic: a goroutine 1 [running]: () :90 +0x4e7 exit status 2
5. Execution order
Check out the execution order of the following code:
func G() { defer func() { ("c") }() F() ("Continue to execute") } func F() { defer func() { if err := recover(); err != nil { ("Catch exception:", err) } ("b") }() panic("a") } func main() { G() }
The order is as follows:
- Call the G() function;
- Call the F() function;
- When you encounter panic in F(), terminate immediately and do not execute the code after panic;
- Execute the defer function in F(), encounter the recover catch error, continue to execute the defer code, and then return;
- Execute the subsequent code of the G() function, and finally execute the defer function in G().
Output:
Catch exception: a
b
Continue to execute
c
5. Capture exception execution order
Check out the execution order of the following code:
func G() { defer func() { if err := recover(); err != nil { ("Catch exception:", err) } ("c") }() F() ("Continue to execute") } func F() { defer func() { ("b") }() panic("a") } func main() { G() }
The order is as follows:
- Call the G() function;
- Call the F() function;
- When you encounter panic in F(), terminate immediately and do not execute the code after panic;
- Execute the defer function in F(). Since there is no recovery, panic will be thrown into G();
- G() When receiving panic, it will not execute subsequent code, and directly execute the defer function;
- The exception a thrown by F() is captured in defer, and then continue to execute and finally exit.
Output:
b
Catch exception: a
c
6. Function execution order
Check out the execution order of the following code:
func G() { defer func() { ("c") }() F() ("Continue to execute") } func F() { defer func() { ("b") }() panic("a") } func main() { G() }
The order is as follows:
- Call the G() function;
- Call the F() function;
- When you encounter panic in F(), terminate immediately and do not execute the code after panic;
- Execute the defer function in F(). Since there is no recovery, panic will be thrown into G();
- G() When receiving panic, it will not execute subsequent code, and directly execute the defer function;
- Since there is no recovery, the exception a thrown by F() is directly thrown, and then exit.
Output:
b
c
panic: a
goroutine 1 [running]:
()
:90 +0x5b
()
:82 +0x48
()
:107 +0x4a5
exit status 2
7. Execution order of exception capture by external functions
Check out the execution order of the following code:
func G() { defer func() { // goroutine outside recover if err := recover(); err != nil { ("Catch exception:", err) } ("c") }() // Create goroutine Call the F function go F() () } func F() { defer func() { ("b") }() // goroutine internally throws panic panic("a") } func main() { G() }
The order is as follows:
- Call the G() function;
- Call the F() function through goroutine;
- When you encounter panic in F(), terminate immediately and do not execute the code after panic;
- Execute the defer function in F(). Since there is no recovery, panic will be thrown into G();
- Since there is no recovery within goroutine, the goroutine external function, that is, the G() function, cannot be captured, and the program crashes and exits directly.
Output:
b
panic: a
goroutine 6 [running]:
()
:96 +0x5b
created by
:87 +0x57
exit status 2
8. Recover’s return value problem
defer func() { if err := recover(); err != nil { ("Catch exception:", ()) } }() panic("a")
Recover returns the interface {} type, not the error type, so if you use it like this, you will get an error:
undefined (type interface {} is interface with no methods)
You can convert it like this:
defer func() { if err := recover(); err != nil { ("Catch exception:", ("%v", err).Error()) } }() panic("a")
Or print the result directly:
defer func() { if err := recover(); err != nil { ("Catch exception:", err) } }() panic("a")
Output:
Catch exception: a
The above is all about this article. In fact, students who have written other languages know that closing the file handle, releasing the lock and other operations are easy to forget. The Go language solves this problem well through defer, but you still have to be careful during use.
Source code address:/yongxinz/gopher/tree/main/sc
The above is the detailed content of the error handling of defer pits that Go programmers have stepped on. For more information about Go defer error handling, please pay attention to my other related articles!