In our daily work, we often use ERR! = nil to determine whether the program or function has reported an error, or defer {recover = err} to determine whether there is a panic serious error, but if you are not careful, it is easy to fall into the trap of Err shadow.

1. Variable scope

package main

import "fmt"

func main(a) {
	x := 100
	func(a) {
		x := 200 // x will shadow outer x
		fmt.Println(x)
	}()

	fmt.Println(x)
}
Copy the code

The output is as follows:

200
100
Copy the code

The x variable is printed as 200 in func and 100 in the outer layer, which is the variable scope. Func {} block (x); func {} block (x); func {} block (x); At this time, variable shadowing occurs in the inner layer x, and the outer layer X is not affected and is still 100.

Let me rewrite it:

package main

import "fmt"

func main(a) {
	x := 100
	func(a) {
		x = 200 // x will override outer x
		fmt.Println(x)
	}()

	fmt.Println(x)
}
Copy the code

The output is as follows:

200
200
Copy the code

At this point, the variable x in func only overwrites the outer layer x and does not define a new variable, so the output of both the inner and outer layers is 200.

2. Err shadow – Nameless error

package main

import (
	"fmt"
	"os"
)

func main(a) {
	fmt.Println("func err1:", test1())
}

func test1(a) error {
	var err error

	defer func(a) {
		fmt.Println("defer err1:", err)
	}()

	if _, err := os.Open("xxx"); err ! =nil {
		return err
	}

	return nil
}
Copy the code

The output is as follows:

defer err1: <nil>
func err1: open xxx: no such file or directory
Copy the code

Results analysis: Test1: func test1 defines the var err error variable, but os.Open uses err := the local err shadow is used. However, as the test1() error return parameter is unnamed, err in defer does not get the errshadow of err. The outer initialization var err error value is still taken. So the output is err1:

.

Just change line 19 to avoid err shadow:

if _, err = os.Open("xxx"); err ! =nil {
		return err
	}
Copy the code

The output is as follows:

defer err1: open xxx: no such file or directory
func err1: open xxx: no such file or directory
Copy the code

3. err shadow – 有名 error

package main

import (
	"fmt"
	"os"
)

func main(a) {
	fmt.Println("func err2:", test2())
}

func test2(a) (err error) {

	defer func(a) {
		fmt.Println("defer err2:", err)
	}()

	if _, err := os.Open("xxx"); err ! =nil {
		return // return without err will compilation error
	}

	return
}
Copy the code

When test2 is run above, errors will be reported by compilation, because go Compiler makes variable shadowing check during compilation, and errors will be reported by compilation directly if any are found. Modify it:

func main(a) {
	fmt.Println("func err3:", test3())
}

func test3(a) (err error) {

	defer func(a) {
		fmt.Println("defer err3:", err)
	}()

	if _, err := os.Open("xxx"); err ! =nil {
		return err
	}

	return
}
Copy the code

The output is as follows:

defer err3: open xxx: no such file or directory
func err3: open xxx: no such file or directory
Copy the code

4. Nested Err Shadow

package main

import (
	"encoding/json"
	"fmt"
	"os"
)

func main(a) {
	fmt.Println("func err4:", test4())
}

func test4(a) (err error) {

	defer func(a) {
		fmt.Println("defer err4:", err)
	}()

	if _, err := os.Open("xxx"); err == nil {
		if err := json.Unmarshal([]byte("{}"), &struct{} {}); err ==nil {
			fmt.Println("OK")}}return
}
Copy the code

The output is as follows:

defer err4: <nil>
func err4: <nil>
Copy the code

Results analysis: Func test4() is a named variable that returns an Err error. When the function is initialized, var err Error defines the corresponding named variable. But the following os.Open or json.Unmarshal uses err := to redefine the err variable, resulting in err shadow, so when the function exits, the outer err is still nil, which is what defer gets.

Change the way you write it:

func main(a) {
	fmt.Println("func err5:", test5())
}

func test5(a) (err error) {

	defer func(a) {
		fmt.Println("defer err5:", err)
	}()

	if _, err = os.Open("xxx"); err == nil {
		if err = json.Unmarshal([]byte("{}"), &struct{} {}); err ==nil {
			fmt.Println("OK")}}return
}
Copy the code

The output is as follows:

defer err5: open xxx: no such file or directory
func err5: open xxx: no such file or directory
Copy the code

5. Summary

Through several examples, this paper analyzes the err shadow problem that is easy to occur in practical work. The essential reason is mainly caused by variable scope, which is mentioned in the official document: An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the entity declared by the inner declaration.

In addition, when naming function return values, we need to consider nameless and named parameters. Under the condition that the code logic is correct, it is recommended to use go Linter or Go Vet to detect variable shadowing that is not detected by the compiler to avoid stepping on the pit.