Variable Shadowing in Golang

Go is an easy language to work with, and it helps you avoid a lot of common mistakes.

But one mistake that’s easy to make in Go, and that the compiler still doesn’t warn you about, is variable shadowing.

Shadowing happens when you declare a variable in one block and then declare it again, with the same name, in a nested block.

You then change the value of the variable believing you’re updating the one from the outer block, but you’re actually only changing the variable in the current block.

Example

In the example below, the variable x is declared in the main block, but right after that, inside the if block, we use := to declare the variable err, which causes x to be recreated in that block.

When the if block ends, that variable stops existing, and the previous instance of x becomes valid again.

package main

import "fmt"

var a = 1

func ret10() (int, error) {
    return 10, nil
}

func main() {
    x := 1
    if a == 1 {
        x, err := ret10()
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println("x =", x)
    }
    fmt.Println("x =", x)
}

As a result, the first fmt.Println prints x = 10, and the second prints x = 1.

Bugs like this are hard to debug, because everything looks normal at first glance.

Experimental Detection

It’s still experimental, but there’s already a way to detect variable shadowing using the go vet tooling.

Installing the Tool

The first step is installing the go vet tool that will help us look for “shadowed” variables in our code.

Use the following command:

go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow

Note that it’s important to have the go/bin directory in your PATH.

Looking for Shadowing

Then just run the following command in your project’s directory.

go vet -vettool=$(which shadow)

For our example, the output will be:

./main.go:14:3: declaration of "x" shadows declaration at line 12

Now we know exactly where the shadowing is happening, which makes the problem much easier to fix.

go vet also sets the errorlevel (or $? in UNIX terms), so you can automate this check in CI tools or even a Makefile to stop the build from continuing.

Tests

It goes without saying that tests help a lot in catching you off guard with variable shadowing.

It would be simple to write tests to make sure your function behaves as expected.

Even without go vet, it might take a while to figure out why the tests are failing, and it might make you scratch your head a few times, but at least you won’t find out about the problem in production — guess who didn’t have test coverage in that situation

Another Kind of Shadowing

Since variables and functions carry the same weight in Go, it’s possible to shadow a function with a variable.

This isn’t a huge problem, because the compiler will throw an error if you try to use a function that’s been overridden by a variable, but it’s still an inconvenience.

Take the following example:

package main

import "fmt"

func main() {
    len := 10 // shadows the builtin len() function
    fmt.Printf("len = %v\n", len)
}

In this example, the variable len is shadowing the runtime’s len function. If we try to use that function anywhere else in our code, the compiler will throw an error.

Cesar Gimenes

Last modified
Tags: