Something to watch for when using = and := operators in Go

So I’ve been playing around with Go a lot, trying to show that I am one of the cool cloud and container kids, y’know. Go is what all the cool cloud and container kids use these days. Anyway, Go is a typed language, which means you have to declare your variables in the form var foo int with the type coming after the variable name (unlike C and similar, where you have the type before the variable name). After that, you can assign values like foo = 34. But Go also has a useful short declaration operator :=. This means you can do foo := 34 without having previously declare the variable. The variable will automatically infer the type of the value (integer in this case) and the value will be assigned to the variable. However, if you try to do something like:

foo := 34
fmt.Println(foo)
foo := "these are words"

// output:
// compiler error
 

This will throw a compiler error because the variable foo has already been set as integer from the first short declaration. You can’t redeclare. At least, not in the same scope. However, you can redeclare the variable if it was declared in a higher scope (say, the global scope). For example:

package main

import "fmt"

var foo int = 34

func main() {
	fmt.Println(foo)
	foo := "these are words"
	fmt.Println(foo)
}

// output:
// 34
// these are words
  

This is an explicit feature of Go, where you can redeclare a variable in a lower scope. However, if a variable is redeclared in a lower scope, any new value in that variable is not going to show up anywhere where that global variable is used (like, say, in another function). We’ll get back to why this is important later.

Another useful thing Go has is multiple assignment similar to Python. You can do things like:

var foo, bar int // declaring two integer variables
foo, bar = 5,6
fmt.Println(foo, bar)

// output: 
// 5 6
 

Does multiple assignment work with the := operator. Yes it does!

foo, bar := 5,6 // declaring and assigning values to two integer variables
fmt.Println(foo, bar)

// output: 
// 5 6
 

A nice thing about the := operator is that it allows you to do multiple assignment with previously declared and defined variables, as long as at least one of the variables on the left hand side has not been previously declared. This is a convenience because Go programs have a lot of foo, err := someFunction() patterns where either foo or err might be a previously undeclared variable and you don’t want worry about declaring the variables and their types ahead of time. You just want some variable to hold a value.

// this is perfectly valid code
var foo int
foo = 67
fmt.Println(foo)
foo, bar := 12, 14
fmt.Println(foo, bar)

// output:
// 67
// 12 14
  

But the compiler will also catch you if you try to assign a value of a different type to a variable in a multiple assignment that was already previously declared in the same scope.

// this is perfectly valid code
foo := 13
fmt.Println(foo)
foo, bar := "bark", "wan"
fmt.Println(foo, bar)

// output:
// compiler error
 

This errors out because we are trying to assign a string value to foo which is already set as integer.

However, if the variable was declared in a higher scope (say, the global scope) and you use it in a multiple assignment, your variable will get redeclared in the current scope.

Look at the following code snippet:

package main

import (
	"fmt"
)

var Foo int = 12

func print() {
	fmt.Println("In print function: ", Foo)
}

func givenum() (int, int) {
	return 44, 55
}

func main() {
	Foo, bar := givenum()
	fmt.Println("In main: Foo: ", Foo, "bar: ", bar)
	print()
}

// output:
// In main: Foo:  44 bar:  55
// In print function:  12
 

When I first encountered this (well, not this program exactly. I was trying to trace down a null pointer exception for a different program and it was maddeningly frustrating before I realized what was happening, but I digress), my initial thought was “this can’t be right. Multiple assignment should assign the new value to the global Foo and the new value should be printed in the print() function. The global variable should be modified, right?” But apparently, in multiple assignment with :=, all variables from a higher scope (like the global scope) WILL BE redeclared. I was working under the impression that multiple assignment with := conveniently performs normal assignment for any previously declared variables. But it does that only for variables declared in the current scope. Variables from higher scopes will be redeclared.

So how do you make sure that you are giving the value to the global variable (and not redeclaring them in the current scope) when you do multiple assignment with :=? You just have to use a temporary variable and then do normal assignment with = for the global variable.

So the program will now look like:

package main

import (
	"fmt"
)

var Foo int = 12

func print() {
	fmt.Println("In print function: ", Foo)
}

func givenum() (int, int) {
	return 44, 55
}

func main() {
	temp, bar := givenum() // assign to a temporary variable
	Foo = temp // assign temporary variable to global variable
	fmt.Println("In main: Foo: ", Foo, "bar: ", bar)
	print()
}

// output:
// In main: Foo:  44 bar:  55
// In print function:  44
 

See also this StackOverflow answer for the rules governing assignment in Go.