Variable scopes and shadowing in Go

Photo by Eneida Nieves on Pexels.com

A lot of things in life are a matter of perspective and visibility and the same applies to variables in Go πŸ˜…. But what is a variable’s scope, how is it defined and what does it mean to shadow a variable in Go?

Let’s (very) loosely say that the scope of a variable declares where this variable is visible from ie. if we have declared a variable at the top of the file then it’s visible from within the entire code of that file.

We can go even deeper than that though and we can break this question down into Go specific terms.

Variable scopes

Go is built on the concept of Blocks, and according with the docs an explicit block is:

… a possibly empty sequence of declarations and statements within matching brace brackets.

https://golang.org/ref/spec#Blocks

That’s easy right? If it’s within a matching pair of { } then it’s a block. But Go also refers to a number of implicit blocks that define the scope of some very well known and used variables and constants.

Universe block

Refers to the entirety of all Go source text and also includes things like the constants true and false, as well as all the definitions of the standard types like int and float32.

πŸ”– Trivia: if you are really into looking how things work under the hood have a look at this file https://golang.org/src/go/types/universe.go. It explains very nicely how the predefined constants, types etc. get added to the scope.

Package block

Is an implicit block that contains the Go source text for everything that is included in a package. Say you have defined a constant or variable at the top level of a file included in a package, this means that this identifier will now be visible by all the code in all the files of the same package even if it’s not exported (not defined with a capitalized name).

πŸ“„ Note: you can find the code for all the examples in this post in this Github repository (https://github.com/efrag/blog-posts) under variable_scope.

Example: we are building a vehicle survey application that will eventually ask our user which means of transport they prefer. We have created a package called vehicle that defines a car and a bicycle sub-package. We define 2 distinct types Car and Bicycle in the files named car.go and bicycle.go .

car.go file that defines the Car type and a helper function to instantiate a Default Car

What’s interesting in this example is our use of the fourDoor constant. This is defined in the constants.go file and it is in scope from within the car.go file because both files belong to the same package.

The file block

Contains all the Go source text in a specific file. This actually overlaps with the package block with regards to variable scoping as identifiers defined at the top of a file will actually be visible not only to the entire file but also to all files defined in the same package.

bicycle.go file that defines the Bicycle type and a helper function that instantiates a list of bicycles

The interesting bit here is the numberOfBicycles variable declared on line 3 at the top of the file. This identifier is visible from everywhere in the file and as we can see it is actually used in the for loop on line 14 to determine how many bicycles this function will generate for us.

The control structure block

Each if, for and switch statement is considered to be in its own implicit block. Let’s use the previous example again. On line 14 in our for loop we declare a new variable i and we set it to 0. This variable is incremented in each iteration and is what eventually will help us terminate our for loop.

We can also see that this variable is used within the for loop on line 17 to help produce a manufacturing year for our bicycle. This variable though only exists within lines 14-20. If we tried to say print it on line 21 we would get an error back from Go that would read

Unresolved reference 'i'

and this is because the variable i is no longer is scope.

Clause block

Each clause in a switch or select statement is also an implicit block. Let’s assume that we have the following piece of code

switch {
	case len(mylist) > 5:
		i := 10
		fmt.Println(i)
	case len(mylist) == 5:
		fmt.Println(i)
}

which defines a switch statement with 2 cases. One where the mylist has more than 5 elements and one where it has exactly 5. In the first case we define a variable i and we print it out. If we try to print out the variable i in the second case we are going to get the same error as before.

Variable shadowing

This is a very interesting concept in computer languages and it’s not specific to Go. A variable is said to be shadowing another variable if it “overrides” the variable in a more specific scope. But let’s look at an example of what this means.

Example that demonstrates shadowing the numberOfBicycles variable

We have defined a function called ShadowingNumberOfBicycles in the bicycle.go file. We are printing the value of the numberOfBicycles variable at the beginning and end of the function and in between we have an if statement that has its own scope as we explained earlier.

Within that if statement we assign a random integer value between 0 and 100 to the numberOfBicycles and if the value is greater than 0 (which is always) then we loop through and print the value incremented by 1 at each iteration.

What’s interesting is that at the end of the function when we print the value of the variable again it’s going to print the value it had at the beginning since the changes we did in the if statement were secluded in that scope and have not affected the outer variable.

Checking for shadowed variables

The easiest way to check for shadowed variables is to use the go vet tool and provide it the -shadow flag. By doing this the tool will produce a report with all the shadowed variables in the code.

$ go vet -shadow github.com/efrag/blog-posts/variable_scope/vehicle/bicycle/bicycle.go 
# command-line-arguments
github.com/efrag/blog-posts/variable_scope/vehicle/bicycle/bicycle.go:33: declaration of "numberOfBicycles" shadows declaration at github.com/efrag/blog-posts/variable_scope/vehicle/bicycle/bicycle.go:8

The output of the tool tells us basically that on line 33 the numberOfBicycles variable is shadowing the variable we have already defined at the File scope on line 8 πŸŽ‰. This can be very useful if you are tracking down a bug that you are not 100% sure how it occurs.

Leave a Reply