Variables

A variable is a storage location for a value. Here's a version of Hello World that uses a variable:

package main

import "fmt"

func main() {
  var x string
  x = "Hello World"
  fmt.Println(x)
}

var x string is an example of a variable declaration. It creates a new variable with a name (x) and a type (string). We then assign a value to that variable (Hello World) and then retrieve the value when we send it to fmt.Println.

A variable is like a box:

The assignment statement x = "Hello World" is how we put a value in the box and when we want to retrieve the value out of the box, we just use its name.

There are many different ways to create variables in go. The grammar states:

VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
    

And many examples are provided:

var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
	i       int
	u, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name]  // map lookup; only interested in "found"

The last example uses a special variable named _ (underscore) which is known as a blank identifier:

The blank identifier may be used like any other identifier in a declaration, but it does not introduce a binding and thus is not declared.

Basically a blank identifier is like a box where you can put things if you don't need them.

Short Variable Declarations

Go has a shorthand for variable declarations using := instead of =:

x := "Hello World"

This is equivalent to:

var x = "Hello World"

This shorthand is only allowed to be used within functions.

Assignment

Variables, as their name would imply, can change their value throughout the lifetime of a program. For example we can write a program like this:

package main

import "fmt"

func main() {
  x := 1
  fmt.Println(x)
  x = 2
  fmt.Println(x)
}

Therefore it's important not to read = as equals but rather takes: "x takes 1" or "x is assigned the value 1", rather than "x equals 1".

Also notice that you can't do this:

x := 1
x := 2

x := 1 both creates a new variable and assigns it a value. But the Go compiler (in general) won't let you create a new variable with the same name. Either give it a new name or use plain =. (The reason it doesn't let you do this is it's almost certainly a mistake to do so)

Order

Assignment follows a strict order. If we break down an assignment statement into its components there are the things on the left side of the equals and the things on the right side: LEFT = RIGHT. The right side is evaluated and then assigned to the left side. This means nothing prevents us from using a variable on the right side that we then assign on the left side:

x = x + 1

This is, in fact, a quite common operation in programming. So common that there is a shorthand syntax:

x += 1

In general you can do this for all the mathematical operations.

Zero Value

So now we know how to assign values to a variable and retrieve them but an obvious question would be: "What is the initial state of a variable?". What does this program do:

package main

import "fmt"

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

It turns out that in Go this case is well defined:

When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for integers, 0.0 for floats, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

Since x is an integer it's default (zero) value is 0.

Scope

So far we've only declared variables inside of our main function. We can also declare them at the top level:

package main

import "fmt"

var x int

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

What this allows us to do is use the variable in other functions:

var x input

func f1() {
  fmt.Println(x)
}
func f2() {
  fmt.Println(x)
}

Whereas this would be invalid:

func f1() {
  var x input
  fmt.Println(x)
}
func f2() {
  fmt.Println(x)
}

f2 does not have access to the variable defined in f1. The valid places a variable can be referenced is known as the variable's scope.

The Go Language specification lays out the scoping rules in precise detail:

The scope of a declared identifier is the extent of source text in which the identifier denotes the specified constant, type, variable, function, label, or package.

Go is lexically scoped using blocks:

  1. The scope of a predeclared identifier is the universe block.

  2. The scope of an identifier denoting a constant, type, variable, or function (but not method) declared at top level (outside any function) is the package block.

  3. The scope of the package name of an imported package is the file block of the file containing the import declaration.

  4. The scope of an identifier denoting a method receiver, function parameter, or result variable is the function body.

  5. The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.

  6. The scope of a type identifier declared inside a function begins at the identifier in the TypeSpec and ends at the end of the innermost containing block.

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.

Let's walk through each of these with some examples.

First notice that in this definition scope refers to more than just variables: identifiers also refer to functions and packages (which we've seen), constants (which we'll see in a sec) and types and labels (which we'll see later).

The scope of a predeclared identifier is the universe block.

Several identifiers are built into the language:

Types:
bool byte complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr
Constants:
true false iota
Zero value:
nil
Functions:
append cap close complex copy delete imag len make new panic print println real recover

All of these identifiers are in the universe block, which means they are available anywhere in a Go program. So what's the universe block?

Blocks can be explicit (created with { and }) or implicit. They are nested within one another:

Things defined in the function scope have access to anything above them (file, package, universe), but the reverse is not true. A variable defined in a function is only accessible within that function or blocks defined inside of it.

The scope of an identifier denoting a constant, type, variable, or function (but not method) declared at top level (outside any function) is the package block.

When you declare a variable at the top level:

package main // f1.go
var x int

That variable is accessible to the entire package, not just the file it's defined in. If we create another file in the same folder we can access x even though it was defined in another file:

package main // f2.go

import "fmt"

func f() {
  fmt.Println(x)
}
The scope of the package name of an imported package is the file block of the file containing the import declaration.

This behavior is the reverse of what we just saw. When you import a package that package name is only accessible in the current file. This would be invalid:

// f1.go
package main

import "fmt"
// f2.go
package main

func f() {
  fmt.Println("Hello World")
}

f2.go needs to import fmt to use it. It's not enough that another file in the same package imports it, because imports have a file-level scope.

The scope of an identifier denoting a method receiver, function parameter, or result variable is the function body.

This will be covered later.

The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.

We don't just have to define variables at the top of a function:

func main() {
  fmt.Println("Hello World")
  x := 5
  fmt.Println(x)
}

In this example we've declared a variable (x) as the second statement in the function body. The above rule states that the scope of that variable is from that point to the end of the block it was declared in - which means we can't access it before it was declared. This is invalid:

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

Which seems obvious, but believe it or not some programming languages don't behave this way. We can also nest blocks:

func main() {
    fmt.Println("Hello World")  // x is out of scope
    {                           // x is out of scope
        x := 5                  // x is in scope
        fmt.Println(x)          // x is in scope
    }                           // x is out of scope again
}

So the variable we declared (x) only lasts till the block it was defined in is closed. This is invalid:

func main() {
    {
        x := 5
    }
    fmt.Println(x)
}
The scope of a type identifier declared inside a function begins at the identifier in the TypeSpec and ends at the end of the innermost containing block.

We will cover types later. Just know that there scope rules are basically the same as variables.

Finally:

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.

Consider this example:

package main

var x int

func main() {
    var x int
    x = 5     // which x?
}

This rule tells us which x we are accessing inside of main. It's always the closest one in terms of scope - in this case the one defined in the function. This is known as shadowing a variable and should generally be avoided since there is now no way to reach the outer x.

Constants

Constants are like variables except they're not allowed to change. They are declared with the const keyword:

package main

const (
  x = 1
  y = 2
)

Because they're not allowed to change this is invalid:

func main() {
    const x = 1
    x = 2
}

One consequence of this is that constants must be declared with an initial value. This is invalid:

func main() {
    const x int
}

However there is a special exception to this rule, and that's with the use of iota. iota is a keyword that allows you to generate constants:

const (
    a = iota
    b = iota
    c = iota
    d = iota
)

iota starts at 0 and is incremented for each subsequent constant. In this example a=0, b=1, c=2 and d=3. Go allows a shorthand for constants when using iota:

const (
    a = iota
    b
    c
    d
)

This code is equivalent to the first example. When we use iota with the first constant we can skip it for all the rest.

← Previous Index Next →