Go variable scope

Go variable scope

introduction to golang variable scopes

You have probably declared a variable at some point in your program and tried to access the code somewhere else but it's now undefined or can't be accessed. Don't panic, that's the concept of scopes.

In Go, variables can be declared either at the package level, the function level or the block level, where the variable is declared determines how and where the variable can be, accessed within the program as well as its lifetime, this is referred to as the scope.

In this article, we would explore the concept of variable scopes in Go and how they can be shadowed while building a simple command-line application that calculates the average of some supplied values.

Blocks and variable scope

Every variable you declare has its scope, just like boundaries that define where you can access the variable. You can't access a variable outside its scope, doing so gives you an error.

A variable’s scope consists of the block it’s declared in and any blocks nested within that block.

Average calculator

To help you get a better understanding of scope in go, We need to write a program that allows a user to type in some numbers and tells them the average of the numbers entered. To get the average of the numbers we simply divide the sum of the values by the number of values.

So our program will need to take in data, compute it and return the information on the command line, that's the first thing we are going to do.

Create a new file average.go

 //average returns the average of numbers supplied
package main

import (
   "fmt"
)

values:= []float64{9.42, 16.56, 27.01}
fmt.Println("values:", values)

func main() {
    var sum float64 = 0
    for _, value : range values {
       sum += value
    }

    average := sum/float64(len(numbers))
    fmt.Println("average": average)
}
  • Values is a slice containing the values we want to calculate
  • We print the values so the user knows what values are being calculated
  • We add each element of the values struct to sum by looping through the values
  • Notice sum was declared outside the for block
  • We assign average the calculated sum divided by the number of values returned by the len method and print the average
  • Go is a static typed language, using the wrong type for a variable can cause your program to fail. Notice how we ensured the length of the values slice is of float64 type by passing it to the float64 method

The Global Scope

Declaring a variable in the global scope allows the variable to be accessed from all parts of the program, they are available all through the lifetime of the program.

When you import packages into the program, their methods become available globally, as in the global scope and can be accessed using the dot notation.

In our example program, the fmt package, the values variable, the main function and the built-in variables and types are in the global scope. This means they can be accessed from every part of the program

 //average returns the average of numbers supplied
package main

import (
   "fmt"
)

values:= []float64{9.42, 16.56, 27.01}
fmt.Println("values:", values)

func main() {
    var sum float64 = 0
    //values here is from the global scope
    for _, value : range values {
       sum += value
    }

    average := sum/float64(len(numbers))
    fmt.Println("average": average)
}

The Local Scope

When you declare a variable within function levels or block levels, they become locally scoped and can only be accessed within their local scope or block level.

Local scope variables are garbage collected when they are no longer needed, this helps to reduce memory consumption.

In our average program, all the variables declared within the main function are available within the main function block and cannot be accessed outside of it, which means they are locally scoped to the main function.

 //average returns the average of numbers supplied
package main

import (
   "fmt"
)

//values is undefined and cannot be accessed from this scope
fmt.Println("values:", values)

func main() {
    values:= []float64{9.42, 16.56, 27.01}

    var sum float64 = 0
    //values here is from the global scope
    for _, value : range values {
       sum += value
    }

    average := sum/float64(len(numbers))
    fmt.Println("average": average)
}

We moved the values variable declaration so it's locally scoped to the main function. Accessing it in the outer scope causes an error since it's now beyond its scope, one way to resolve this is to move the average variable declaration back to the global scope.

Shadow variables

We talked about how variables in the outer scope can be accessed in the inner scope. heads up, when you declare a variable you should make sure it doesn't have the same name as any existing variables, functions, packages, types or all other variables else the existing variable get shadowed - that is, take precedence over the existing variable.

You would expect Go to complain about this by returning an error, but that's only when the two variables exist in the same scope, but in a case where the new variable declared with the same name exists in the inner scope, go simply assigns the new value and all through the inner scope, so when you access that variable in the inner scope you get the new value but the outer scope remains the same.

 //average returns the average of numbers supplied
package main

import (
   "fmt"
)

values:= []float64{9.42, 16.56, 27.01}

func main() {
     //values shadows the one declared in the outer variable
     var values string = "go"
     //string would now refers to a declared variable
     var string string = "hello"
     //fmt is now a variable of string type
     var fmt string = "shadowed variable"

     //this returns 2
     fmt.Println("value:", values)

     var sum float64 = 0

     //Oops! valuesrefer to a string in this scope and you can't loop through it
    for _, value : range values {
       sum += value
     }
}

In the sample program, we shadowed a couple of different variables but if we try to access the type, function, or package the variables are shadowing, it results in compile errors:

  • Values now have a string type in the function main scope
  • string would refer to the variable declared, not the string type
  • fmt also now refers to the declared variable and bot the fmt package imported
  • The Println cannot be accessed on the fmt variable within the function main scope since it now refers to a string variable
  • We can't loop through a string, so the for loop breaks the code

Just like you must have imagined by now, shadowing a variable can quickly become a bad thing and cause your program to fail unexpectedly, so you should always ensure you are using non-conflicting names for your variables, an example is an idiomatic practice - that is community standards, of using err when getting returned errors instead for error since this conflicts with the error type.

Conclusion

You now know about the global and local variable scope in go, you also learnt about shadow variables - that is taking precedence over the existing variable and how to avoid that.

pour yourself some coffee - you earned it! 😉

Don't forget to like, comment and share this article and connect with me on Twitter @robogeke95, LinkedIn, and Github.