Go interview series (6) – what the hell is err shadow?

Time:2021-12-24

In our daily work, we often useerr != nilTo determine whether the program or function reports an error, or usedefer {recover = err}To determine whether there ispanicSerious error, but if you don’t pay attention, it’s easy to fall intoerr shadowA trap.

1. Variable scope

package main

import "fmt"

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

    fmt.Println(x)
}

The output is as follows:

200
100

Result analysis:
xVariable infuncPrinted as200, print as100, this is the scope of the variable(variable scope)。funcVariables insidexIs a new variable, just with the outer layerxDuplicate name(variable redeclaration)At this time, the inner layerxThe scope of is limited tofunc {} blockAnd the outer layerxThe scope of ismain {} blockAt this time, the inner layer variablexIt happenedvariable shadowing, outer layerxNot affected, still100

Change the wording:

package main

import "fmt"

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

    fmt.Println(x)
}

The output is as follows:

200
200

At this point,funcVariables insidexIt just covers the outer layerx, no new variables are defined, so both inner and outer outputs are200

2. Err shadow – unknown error

package main

import (
    "fmt"
    "os"
)

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

func test1() error {
    var err error

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

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

    return nil
}

The output is as follows:

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

Result analysis:
func test1First definedvar err errorVariable, but the followingos.OpenError reporting useerr :=Be localerr shadowYes, although it is explicitly usedreturn errAn error was returned, but becausetest1() errorThe return parameter is anonymous(unnamed variable), resulting indeferinerrFailed to geterr shadowError oferr, the outer initialization is still takenvar err errorValue, so the output iserr1: <nil>

You just need to19If you change the line, you can avoid iterr shadow

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

The output is as follows:

defer err1: open xxx: no such file or directory
func err1: open xxx: no such file or directory

3. Err shadow – name error

package main

import (
    "fmt"
    "os"
)

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

func test2() (err error) {

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

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

    return
}

abovetest2There will be compilation errors during operation, which isgo compilerDone at compile timevariable shadowingCheck and directly compile and report errors if any. Just modify it:

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

func test3() (err error) {

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

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

    return
}

The output is as follows:

defer err3: open xxx: no such file or directory
func err3: open xxx: no such file or directory

4. Nested err shadow

package main

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

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

func test4() (err error) {

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

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

    return
}

The output is as follows:

defer err4: <nil>
func err4: <nil>

Result analysis:
func test4()Is a named returnerr error, the function initializesvar err errorDefine the corresponding named variable(named variable), but the followingos.Openorjson.UnmarshalAll usederr :=redefinitionerrVariable, resulting inerr shadowTherefore, when the function exits, the outer layererrStillnildeferAcquisition, that isnil

Just change the wording:

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

func test5() (err error) {

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

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

    return
}

The output is as follows:

defer err5: open xxx: no such file or directory
func err5: open xxx: no such file or directory

5. Summary

Through several examples, this paper analyzes the problems that are easy to appear in practical workerr shadowThe essential reason for the problem is mainly caused by the variable scope. It 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, in the naming of function return values, we need to consider the nameless and famous parameters. It is recommended to use tools when the code logic is correctgo linterorgo vetTo detect what the compiler didn’t detectvariable shadowing, avoid stepping on the pit.