Global Variables

here’s a Go program showing a global variable:

var x int = 5

func main() {
    fmt.Println(x)
}

it’s okay in Go if you declare a global after the function it’s used in:

func main() {
    fmt.Println(x)
}

var x int = 5

for local variables, this is okay in Go:

func main() {
    var x int = 5
    fmt.Println(x)
}

but this is a compile error because x is declared before it’s first use:

func main() {
    fmt.Println(x)  // compiler error: x used before it is defined
    var x int = 5
}

What’s so bad about global variables?

on the good side, they take up a fixed amount of memory that is known at compile-time, so no run-time is wasted allocation/de-allocating them

here’s a practical example of how you might want to use a global variable:

var hCount int = 0

func h(n int) int {
    hCount++
    return 2*n + 1
}

func main() {
    total := 0
    for i := 0; i < 10; i++ {
        total += h(i)
    }
    fmt.Printf("total=%v\n", total)
    fmt.Printf("h called %v times\n", hCount)
}

the global hCount keeps track of how many times h is called throughout the entire program

hCount can’t be local, because then it would not be able to count calls to h outside the function it is local to, e.g.:

func h(n int) int {
    hCount++        // compiler error: hCount is undefined
    return 2*n + 1
}

func main() {
    var hCount int = 0 // hCount can only be reference inside main
    // ...
}

only code that occurs inside main after hCount is defined is permitted to refer to hCount

in Go, local variables like hCount have static scope, also called lexical scope

you can figure out the scope of hCount by looking at the Go source: it goes from the var statement to the inner-most matching }

don’t confuse a variable’s scope with its lifetime

the lifetime of hCount is the entire run of the program, but it’s scope is the code inside main

the lifetime of a global variable is also the entire run of the program

What’s so bad about global variables?

a big problem with global variables is that they can be used to break the usual rules of functions that we assume are true in math

for example, if \(f(n)\) is a mathematical function that returns an integer, then, in math, \(f(3)\) will always return the same value no matter when, where, or how many times you call \(f(3)\)

but global variables let us write functions like this:

var x int = 2

func f(n int) int {
    x++
    return n + x
}

func main() {
    fmt.Println(f(3))        // 6
    fmt.Println(f(3))        // 7
    fmt.Println(f(3))        // 8
    fmt.Println(f(3) - f(3)) // -1
}

the last example shows that f(3) - f(3) is not 0, and so f(3) != f(3)

in math, that’s crazy!

in math, if \(f\) is a function from, say, integers to integers, then it is true that \(f(n) = f(n)`\) for all integers \(n\)

in math, if a function doesn’t always return the same value for the same input, then, by definition, it’s not a function!

you might say that the problem with f is that it modifies the global x, and it is this modification of a global variable that is the problem

but even just reading a global in a function can cause problems, e.g.:

var x int = 2
func g(n int) int {
    return n + x
}

func main() {
    fmt.Println(g(3))        // 5 (good)
    fmt.Println(g(3))        // 5 (good)
    fmt.Println(g(3))        // 5 (good)
    fmt.Println(g(3) - g(3)) // 0 (good)
}

these examples are okay, but nothing stops us from modifying x between function calls, e.g.:

func main() {
    fmt.Println(g(3))        // 5
    fmt.Println(g(3))        // 5
    x++
    fmt.Println(g(3))        // 6  (what?!)
    fmt.Println(g(3) - g(3)) // 0
}

when you think about it, this is a pretty crazy example

by modifying x, we’ve caused g to behave differently!

imagine a big, complex real-world program with 1000s of lines of code

and imagine that program uses a few global variables

I would be worried that we might run into a problem like this, unintentionally changing the behaviour of a function by incorrectly modifying a global variable

experience shows that, in general, minimizing the scope of variables helps prevent errors

What’s so bad about global variables?

one fix to this problem is to do what they do in math and simply not allow global variables!

for instance, Java sort of does this, although it has essentially the same problem with static variables in classes

some functional programming languages, like Haskell, are even more strict and do not allow any variables — local or global! — to be modified

a compromise that some languages encourage is to allow global constants, i.e. variables that can be read but not written

since no code can modify a function, this prevents the many of the problems of using global variables

Shadowing

globals and locals can have the same name:

var x int = 2

func main() {
    fmt.Println(x)  // 2
    var x int = 5
    fmt.Println(x)  // 5
}

after the var statement, the global x is shadowed by local x and so cannot be accessed

Go’s := and = operators can be the source of errors if you are not careful, e.g.:

func main() {
    for x := 1; x <= 5; x++ {
        x := 2 * x               // := used
        fmt.Println(x)
    }
}

// prints: 2 4 6 8 10

the only difference in the following code is that one := has been changed to =:

func main() {
    for x := 1; x <= 5; x++ {
        x = 2 * x                // = used
        fmt.Println(x)
    }
}

// prints: 2 6

Go lets you nest code blocks, so you can also get functions such as this:

func nested() {
    x := 1
    if x > 0 {
        x := 2
        if x > 1 {
            x := 3
            fmt.Println(x) // 3
        }
    }
}

each { marks the beginning of a new code block, and each matching } marks the end of the block