The Go language uses the usual methods of controlling the flow of execution: if, for, switch, goto. There is also a go statement to run the code in a separate go-procedure. And now I would like to discuss less common ways: defer, panic and recover.
The
defer command places a function call on the list. This list of deferred calls is executed after the enclosing function completes execution. Defer is usually used to simplify the functions that deal with the release of a resource.
For example, look at a function that opens two files and copies the contents from one file to another:
')
func CopyFile (dstName, srcName string ) (written int64 , err os.Error ) {
src, err: = os.Open (srcName, os.O_RDONLY, 0 )
if err! = nil {
return
}
dst, err: = os.Open (dstName, os.O_WRONLY | os.O_CREATE, 0644 )
if err! = nil {
return
}
written, err = io.Copy (dst, src)
dst.Close ()
src.Close ()
return
}
The code is working, but there is an error. If the second os.Open call fails, the function will terminate, leaving the first file open. This is easily fixed by adding the src.Close () call before the second return, but if the function is more complicated, then this problem can be missed. Entering defer commands, you can make the files close under any conditions:
func CopyFile (dstName, srcName string ) (written int64 , err os.Error ) {
src, err: = os.Open (srcName, os.O_RDONLY, 0 )
if err! = nil {
return
}
defer src.Close ()
dst, err: = os.Open (dstName, os.O_WRONLY | os.O_CREATE, 0644 )
if err! = nil {
return
}
defer dst.Close ()
return io.Copy (dst, src)
}
The defer command allows you to think about closing the file immediately after opening it, ensuring that the file
is closed, regardless of the number of exit points from the function.
The behavior of defer commands is simple and predictable. There are three easy rules:
1.
The deferred function call arguments are evaluated when the defer command is evaluated.In this example, the expression "i" is evaluated when the Println call is deferred. A delayed call will print “0” after returning from the function.
func a () {
i: = 0
defer fmt.Println (i)
i ++
return
}
2.
Deferred function calls are made in the LIFO order: the last deferred call will be called first - after the enclosing function completes execution.This function will print "3210":
func b () {
for i: = 0 ; i < 4 ; i ++ {
defer fmt.Print (i)
}
}
3.
Deferred functions can read and set the named return values ​​of the enclosing function.In this example, the deferred function increases the return value i after the enclosing function completes execution. So, this function returns 2:
func c () (i int ) {
defer func () {i ++} ()
return 1
}
This is a convenient way to change the error code returned by a function. Soon we will see an example of this.
Panic is a built-in function that stops the normal flow of control and starts panicking. When the function F calls panic, the execution of F stops, all pending calls to F are executed normally, then F returns control to the calling function. For the calling function, calling F behaves like calling panic. The process continues up the stack until all functions in the current go procedure complete the execution, after which the program crashes. Panic can be caused by a direct call to panic, as well as due to runtime errors, such as access outside the array.
Recover is a built-in function that regains control over a panicked go procedure. Recover is only useful inside a deferred function call. During normal execution, recover returns nil and has no other effects. If the current go procedure panics, then the call to recover returns the value that was passed to panic and restores normal execution.
Here is an example program that demonstrates the panic and defer mechanics:
package main
import "fmt"
func main () {
f ()
fmt.Println ( "Returned normally from f." )
}
func f () {
defer func () {
if r: = recover (); r! = nil {
fmt.Println ( "Recovered in f" , r)
}
} ()
fmt.Println ( "Calling g." )
g ( 0 )
fmt.Println ( "Returned normally from g." )
}
func g (i int ) {
if i> 3 {
fmt.Println ( "Panicking!" )
panic (fmt.Sprintf ( "% v" , i))
}
defer fmt.Println ( "Defer in g" , i)
fmt.Println ( "Printing in g" , i)
g (i + 1 )
}
The function g takes the integer i as an input and panics if i is greater than 3, or it will wind itself with the argument i + 1. The f function defers the function that calls recover and prints the restored value (if it is not empty). Try to imagine what this program will display before reading further.
The program will output:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
If we remove the deferred function call from f, then the panic does not stop and reaches the top of the call stack of the go procedure, stopping the program. So the modified program will output:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
panic PC = 0x2a9cd8
[Call stack omitted]
For a real example of using
panic and
recover, see
the json package from the standard Go library. It decodes JSON encoded data using a set of recursive functions. When an incorrectly generated JSON arrives at the input, the parser calls panic to expand the stack to the top call, which recovers from the panic and returns the appropriate error code (see the “error” and “unmarshal”
functions in
decode.go ). A similar example of such a technique is in the
Compile procedure of the
regexp package. There is an agreement that in Go libraries, even if the package uses panic inside, its external API returns explicit error codes.
Other uses of
defer (besides the above file.Close () example) include releasing the mutex:
mu.Lock ()
defer mu.Unlock ()
print footer:
printHeader ()
defer printFooter ()
And much more.
In summary, the defer command (with or without panic and recover) provides an unusual and powerful mechanism for controlling the flow of execution. It can be used to simulate various possibilities, which are responsible for special structures in other programming languages. Try it.
PS Comments on typos and other inaccuracies, please communicate by personal messages, as for various reasons I can only correct them in the evening.