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