Go has the usual control flow mechanisms: if, for, Switch, goto. It also uses the Go statement to run code in a specific Goroutine, and HERE I want to discuss some of the less common ones: defer, Panic, recover.

The defer statement puts the function call into the list. The list of saved calls will be executed after the surrounding functions return. Defer is typically used to simplify various cleanup functions.

For example, let’s look at a function that opens two files and copies the contents of one file to the other:

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

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

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

This function works, but there is a bug. If the call to OS.create fails, this function returns without closing the source file. This problem can be easily solved by calling src.close before the second return statement, but if the function is complex, the problem will not be so easily discovered and resolved. By introducing the defer statement, we can ensure that the file is always closed:

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

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

    return io.Copy(dst, src)
}
Copy the code

The defer statement allows us to close each file immediately after we open it, ensuring that the file will be closed no matter how many return statements there are in the function

The behavior of the defer statement is straightforward and predictable. There are three simple rules:

  1. When the defer statement is evaluated, the parameters of the defer function are evaluated. In this case, expression I is evaluated when the Println call is delayed, and the delayed call prints 0 after the function returns.

    func a(a) {
        i := 0
        defer fmt.Println(i)
        i++
        return
    }
    Copy the code
  2. After the surrounding functions return, the defer function will be executed in last-in, first-out order. This function prints “3210” :

    func b(a) {
        for i := 0; i < 4; i++ {
            defer fmt.Print(i)
        }
    }
    Copy the code
  3. The defer function can read the specified return value of the return function and copy it to the return function. In the following example, after the surrounding functions return, the defer function will increment the return value I. Therefore, this function will return 2:

    func c(a) (i int) {
        defer func(a) { i++ }()
        return 1
    }
    Copy the code

This makes it easy to modify the error return value of the function; We’ll see an example next.

Panic is a built-in function that stops regular control flow and starts panicking, and when function F calls Panic, execution of F will stop and any defer functions in F will execute normally, and F will then return to its caller. To the caller, F behaves like a panic call. The process continues to execute the stack until all functions of the current Goroutine are returned, at which point the program crashes. Panic can be started by calling Panic directly. It can also be caused by a runtime error, such as an out-of-bounds array access.

Recover is a built-in function that can regain The goroutine of Panicking. Recover only takes effect inside the defer function. During normal execution, the call to Recover will return nil, nothing else. If the current Goroutine is panicking, calling Recover captures the value to Panic and resumes normal execution.

Here is a sample program to demonstrate the mechanics of Panic and defer:

package main

import "fmt"

func main(a) {
    f()
    fmt.Println("Returned normally from f.")}func f(a) {
    defer func(a) {
        if r := recover(a); 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)}Copy the code

The function g takes the integer I, and if I >3 it causes panic, otherwise it calls itself with the argument I +1. Function f delays calling recover and prints the recovered value (if non-nil). Before reading on, try to think about what the output of this program might be.

The output of the program is as follows:

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.
Copy the code

If we remove the defer function from f, panic will not recover and will terminate the program after reaching the top of the Goroutine call stack. Here is the output of the program after modification:

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
[stack trace omitted]
Copy the code

For real-world examples of Panic and Recover, see the JSON package code for the Go standard library. It encodes the interface using a set of recursive functions. If an error occurs while traversing the value, panic is called to push the stack back to the top-level function call, which recovers the appropriate error value from Panic (see encodeState type “error” and “marshal” methods in encode.go)

The convention of the Go library is that if panic is used inside a package, the external API will still display the error return value explicitly

Another use of defer involves releasing the mutex

mu.Lock()
defer mu.Unlock()
Copy the code

The tail as a printed page

printHeader()
defer printFooter()
Copy the code

There are more uses that are not given

In summary, the defer statement (followed or not followed by Panic, Recover) provides an unusual and powerful mechanism for controlling flow. It can be used to model many features implemented by special constructs in other programming languages. Give it a try.

The original link