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.
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.
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)
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.
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
.
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:
The scope of a predeclared identifier is the universe block.
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.
The scope of the package name of an imported package is the file block of the file containing the import declaration.
The scope of an identifier denoting a method receiver, function parameter, or result variable is the function body.
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.
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 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 → |