A var
declaration creates a variable of a particular type, attaches a name to
it, and sets its initial value. Each declaration has the general form
var name type = expression
Either the type or the =
expression
part may be omitted, but
not both.
If the type is omitted, it is determined by the initializer
expression.
If the expression is omitted, the initial value is the zero value
for the type, which is 0
for numbers, false
for booleans,
""
for strings, and nil
for interfaces and reference
types (slice, pointer, map, channel, function).
The zero value of an aggregate type like an array or a struct has the
zero value of all of its elements or fields.
The zero-value mechanism ensures that a variable always holds a well-defined value of its type; in Go there is no such thing as an uninitialized variable. This simplifies code and often ensures sensible behavior of boundary conditions without extra work. For example,
var s string fmt.Println(s) // ""
prints an empty string, rather than causing some kind of error or unpredictable behavior. Go programmers often go to some effort to make the zero value of a more complicated type meaningful, so that variables begin life in a useful state.
It is possible to declare and optionally initialize a set of variables in a single declaration, with a matching list of expressions. Omitting the type allows declaration of multiple variables of different types:
var i, j, k int // int, int, int var b, f, s = true, 2.3, "four" // bool, float64, string
Initializers may be literal values or arbitrary expressions.
Package-level variables are initialized before main
begins
(§2.6.2),
and local variables are initialized as their declarations
are encountered during function execution.
A set of variables can also be initialized by calling a function that returns multiple values:
var f, err = os.Open(name) // os.Open returns a file and an error
Within a function, an alternate form called a short variable declaration
may be used to declare and initialize local variables.
It takes the form name
:=
expression
, and the type of
name
is determined by the type of expression
.
Here are three of the many short variable declarations in the
lissajous
function (§1.4):
anim := gif.GIF{LoopCount: nframes} freq := rand.Float64() * 3.0 t := 0.0
Because of their brevity and flexibility, short variable declarations are
used to declare and initialize the majority of local variables.
A var
declaration tends to be reserved for local variables that
need an explicit type that differs from that of the initializer
expression, or for when the variable will be assigned a value later
and its initial value is unimportant.
i := 100 // an int var boiling float64 = 100 // a float64 var names []string var err error var p Point
As with var
declarations, multiple variables may be declared and initialized in
the same short variable declaration,
i, j := 0, 1
but declarations with multiple initializer expressions should be used
only when they help readability, such as for short and natural
groupings like the initialization part of a for
loop.
Keep in mind that :=
is a declaration, whereas =
is an
assignment.
A multi-variable declaration should not be confused with a tuple assignment
(§2.4.1),
in which each variable on the left-hand side
is assigned the corresponding value from the right-hand side:
i, j = j, i // swap values of i and j
Like ordinary var
declarations, short variable declarations may
be used for calls to functions like os.Open
that return two or more
values:
f, err := os.Open(name) if err != nil { return err } // ...use f... f.Close()
One subtle but important point: a short variable declaration does not necessarily declare all the variables on its left-hand side. If some of them were already declared in the same lexical block (§2.7), then the short variable declaration acts like an assignment to those variables.
In the code below, the first statement declares both in
and
err
.
The second declares out
but only assigns
a value to the existing err
variable.
in, err := os.Open(infile) // ... out, err := os.Create(outfile)
A short variable declaration must declare at least one new variable, however, so this code will not compile:
f, err := os.Open(infile) // ... f, err := os.Create(outfile) // compile error: no new variables
The fix is to use an ordinary assignment for the second statement.
A short variable declaration acts like an assignment only to variables that were already declared in the same lexical block; declarations in an outer block are ignored. We’ll see examples of this at the end of the chapter.
A variable is a piece of storage containing a value.
Variables created by declarations are identified by a name, such as
x
, but many variables are identified only by expressions like
x[i]
or x.f
.
All these expressions read the value of a variable, except when they
appear on the left-hand side of an assignment, in which case a new value
is assigned to the variable.
A pointer value is the address of a variable. A pointer is thus the location at which a value is stored. Not every value has an address, but every variable does. With a pointer, we can read or update the value of a variable indirectly, without using or even knowing the name of the variable, if indeed it has a name.
If a variable is declared var x int
,
the expression &x
(“address of x
”) yields a pointer to an integer variable, that is, a
value of type *int
, which is pronounced “pointer to int.” If this
value is called p
, we say “p
points to x
,” or equivalently “p
contains the address of x
.” The variable to which p
points is
written *p
. The expression *p
yields the value of that variable,
an int
, but since *p
denotes a variable, it may also appear on the
left-hand side of an assignment, in which case the assignment updates
the variable.
x := 1 p := &x // p, of type *int, points to x fmt.Println(*p) // "1" *p = 2 // equivalent to x = 2 fmt.Println(x) // "2"
Each component of a variable of aggregate type—a field of a struct or an element of an array—is also a variable and thus has an address too.
Variables are sometimes described as addressable values.
Expressions that denote variables are the only expressions
to which the address-of operator &
may be applied.
The zero value for a pointer of any type is nil
. The test
p != nil
is true if p
points to a variable.
Pointers are comparable; two pointers are equal if and only if they point to the same variable
or both are nil.
var x, y int fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
It is perfectly safe for a function to return the
address of a local variable.
For instance, in the code below,
the local variable v
created by this particular call to
f
will remain in existence even after the call has returned, and
the pointer p
will still refer to it:
var p = f() func f() *int { v := 1 return &v }
Each call of f
returns a distinct value:
fmt.Println(f() == f()) // "false"
Because a pointer contains the address of a variable, passing a pointer argument to a function makes it possible for the function to update the variable that was indirectly passed. For example, this function increments the variable that its argument points to and returns the new value of the variable so it may be used in an expression:
func incr(p *int) int { *p++ // increments what p points to; does not change p return *p } v := 1 incr(&v) // side effect: v is now 2 fmt.Println(incr(&v)) // "3" (and v is 3)
Each time we take the address of a variable or copy a pointer,
we create new aliases or ways to identify the same variable.
For example, *p
is an alias for v
.
Pointer aliasing is useful because it allows us to access a variable
without using its name, but this is a double-edged sword: to find
all the statements that access a variable, we have to know all its
aliases.
It’s not just pointers that create aliases; aliasing also
occurs when we copy values of other reference types like slices,
maps, and channels, and even structs, arrays, and interfaces that
contain these types.
Pointers are key to the flag
package, which uses a program’s command-line
arguments to set the values of certain variables
distributed throughout the program. To illustrate, this variation on
the earlier echo
command takes two optional flags: -n
causes echo
to omit the trailing newline that would normally be printed, and -s sep
causes it to separate the output arguments by the contents of the
string sep
instead of the default single space.
Since this is our fourth version,
the package is called gopl.io/ch2/echo4
.
// Echo4 prints its command-line arguments. package main import ( "flag" "fmt" "strings" ) var n = flag.Bool("n", false, "omit trailing newline") var sep = flag.String("s", " ", "separator") func main() { flag.Parse() fmt.Print(strings.Join(flag.Args(), *sep)) if !*n { fmt.Println() } }
The function flag.Bool
creates a new flag variable of type bool
.
It takes three arguments: the name of the flag ("n"
), the variable’s
default value (false
), and a message
that will be printed if the user provides an invalid argument, an
invalid flag, or -h
or -help
.
Similarly, flag.String
takes a name, a default value, and a message,
and creates a string
variable.
The variables sep
and n
are pointers to the
flag variables, which must be accessed indirectly as *sep
and *n
.
When the program is run, it must call flag.Parse
before the flags are used,
to update the flag variables from their default values.
The non-flag arguments are available from flag.Args()
as a slice of strings. If
flag.Parse
encounters an error, it prints a usage message
and calls os.Exit(2)
to terminate the program.
Let’s run some test cases on echo
:
$ go build gopl.io/ch2/echo4 $ ./echo4 a bc def a bc def $ ./echo4 -s / a bc def a/bc/def $ ./echo4 -n a bc def a bc def$ $ ./echo4 -help Usage of ./echo4: -n omit trailing newline -s string separator (default " ")
new
Function
Another way to create a variable is to use the built-in function new
.
The expression new(T)
creates an unnamed variable of type T
,
initializes it to the zero value of T
,
and returns its address, which is a value of type *T
.
p := new(int) // p, of type *int, points to an unnamed int variable fmt.Println(*p) // "0" *p = 2 // sets the unnamed int to 2 fmt.Println(*p) // "2"
A variable created with new
is no different from an ordinary
local variable whose address is taken, except that there’s no need to
invent (and declare) a dummy name, and we can use new(T)
in an
expression.
Thus new
is only a syntactic convenience, not a fundamental notion:
the two newInt
functions below have identical behaviors.
func newInt() *int { func newInt() *int { return new(int) var dummy int } return &dummy }
Each call to new
returns a distinct variable with a unique address:
p := new(int) q := new(int) fmt.Println(p == q) // "false"
There is one exception to this rule: two variables whose type carries no
information and is therefore of size
zero, such as struct{}
or [0]int
, may, depending on the
implementation, have the same address.
The new
function is relatively rarely used
because the most common unnamed
variables are of struct types, for which the struct literal syntax
(§4.4.1) is more flexible.
Since new
is a predeclared function, not a keyword, it’s possible
to redefine the name for something else within a function, for
example:
func delta(old, new int) int { return new - old }
Of course, within delta
, the built-in new
function is unavailable.
The lifetime of a variable is the interval of time during which it exists as the program executes. The lifetime of a package-level variable is the entire execution of the program. By contrast, local variables have dynamic lifetimes: a new instance is created each time the declaration statement is executed, and the variable lives on until it becomes unreachable, at which point its storage may be recycled. Function parameters and results are local variables too; they are created each time their enclosing function is called.
For example, in this excerpt from the Lissajous program of Section 1.4,
for t := 0.0; t < cycles*2*math.Pi; t += res { x := math.Sin(t) y := math.Sin(t*freq + phase) img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex) }
the variable t
is created each time the for
loop begins, and
new variables x
and y
are created on each iteration of
the loop.
How does the garbage collector know that a variable’s storage can be reclaimed? The full story is much more detailed than we need here, but the basic idea is that every package-level variable, and every local variable of each currently active function, can potentially be the start or root of a path to the variable in question, following pointers and other kinds of references that ultimately lead to the variable. If no such path exists, the variable has become unreachable, so it can no longer affect the rest of the computation.
Because the lifetime of a variable is determined only by whether or not it is reachable, a local variable may outlive a single iteration of the enclosing loop. It may continue to exist even after its enclosing function has returned.
A compiler may choose to allocate local variables on the heap or on
the stack but, perhaps surprisingly, this choice is not determined by
whether var
or new
was used to declare the variable.
var global *int func f() { func g() { var x int y := new(int) x = 1 *y = 1 global = &x } }
Here, x
must be heap-allocated because it is still reachable
from the variable global
after f
has returned, despite being
declared as a local variable; we say x
escapes
from f
.
Conversely, when g
returns, the variable *y
becomes
unreachable and can be recycled.
Since *y
does not escape from g
, it’s safe for the
compiler to allocate *y
on the stack, even though it was allocated
with new
.
In any case, the notion of escaping is not something that you need
to worry about in order to write correct code, though
it’s good to keep in mind during performance optimization, since each
variable that escapes requires an extra memory allocation.
Garbage collection is a tremendous help in writing correct programs, but it does not relieve you of the burden of thinking about memory. You don’t need to explicitly allocate and free memory, but to write efficient programs you still need to be aware of the lifetime of variables. For example, keeping unnecessary pointers to short-lived objects within long-lived objects, especially global variables, will prevent the garbage collector from reclaiming the short-lived objects.
18.189.180.76