Golang Variable Shadowing
Recently I’ve been playing with some code in Go. Code was quite simple, but what I wanted is to simplify error handling a little bit and make code more readable.
As many Go developers know, error handling in Go is usually done this way:
func templateToFile(templateFilename string, filename string, data interface{}) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
if err != nil {
return err
}
defer f.Close()
t, err := template.ParseFiles(templateFilename)
if err != nil {
return err
}
return t.Execute(f, data)
}
I’ve used to it too, but I was wondering whether this can be simplified. So I decided to use named return values and reorganize my code to something like this
func templateToFile(templateFilename string, filename string, data interface{}) (err error) {
if f, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666); err == nil {
defer f.Close()
if t, err := template.ParseFiles(templateFilename); err == nil {
return t.Execute(f, data)
}
}
return
}
As you can see, here I have named return variable err
and in each operation we put something there so that if os.OpenFile
fails, you’ll get err != nil
and it will be returned. Theoretically this looks nice, but it doesn’t work because of variable shadowing. In this particular example err
in return value, err
in first if block and err
in second if block are different variables!
My assumption that my code would work was based on this statement from Golang Specification:
Redeclaration does not introduce a new variable; it just assigns a new value to the original.
That’s true, redeclaration just assigns a new value to the original but there’s a small note… if they’re in the same block.
Information from the specification
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.
So, if you use short assignment (:=
) to declare variable with the same name in inner block, you will have new variable that has the same name as in outer block and any changes of the value won’t affect variable from outer block.
Really good sample of this behavior you can find in 50 Shades of Go:
func main() {
x := 1
fmt.Println(x) //prints 1
{
fmt.Println(x) //prints 1
x := 2
fmt.Println(x) //prints 2
}
fmt.Println(x) //prints 1 (bad if you need 2)
}
Detect shadowing
Variable shadowing can be detected by vet
command from Go. Call it this way:
go tool vet --shadow file.go
When you run this command, you’ll see shadowing issues in the file you’ve provided.
Example:
main.go:14: declaration of "err" shadows declaration at main.go:13
Conclusion
Shadowed variables is basic type of errors and easily can be detected. However, when you have never faced this error before, you can spend some time trying to understand what’s going on in your code.
Be careful with shadowing and always vet your code!