See icza's answer, and particularly the "note beforehand" section, for your specific case. For a general discussion of pointers, read on; note that only some of this is specific to Go itself.
What a pointer does for you is to give you a level of indirection. Some languages—Go included—use a "pass by value" mechanism, where regardless of whether you pass a constant value or a variable to some function:
f(3)
or:
f(x)
the function f
receives the value, not the name of the variable or anything remotely like that. (Other languages are different, having "pass by name",1 "pass by reference", or "value-result" semantics in some cases. See also Why [do] so many languages [use pass] by value? The fact that f
receives the value, not the variable's name or anything like that, is helpful when there isn't a variable, as in the f(3)
case, or for:
f(x + y)
where we have to do a summation first and hence there's no single variable involved.
Now, in Go in particular, functions can and very often do have multiple return values:
func g(a int) (bool, int, error) { ... }
so if we want to be able to update something, we can just write:
ok, x, err = g(x)
which gathers all three values, including the updated x
, right where we want it. But this does expose the details of the updated x
, and/or maybe it is inconvenient. If we want to give some function permission to change some value(s) stored in some variable, we can define our function to take a pointer to that variable:
func g2(a *int) (bool, error) { ... }
Now instead of ok, x, err = g(x)
we can write ok, err = g2(&x)
.
This is not much of an improvement at all, for this particular case. But suppose instead of a simple int
, x
is now a structure with a bunch of complicated state, e.g., something that will read input from a series of files, switching to the next file automatically:
x := multiFileReader(...)
Now if we want parts of the multi-file-reader to be able to access various fields in whatever structure x
represents, we can make x
itself a pointer variable, pointing to the struct
. Then:
str, err := readNextString(x)
passes a pointer (because x
is a pointer) which allows readNextString
to update some of the fields within x
. (This same general logic applies if x
is a method, e.g, an io.Reader
, but in this case we start using interface
, which adds a bunch of additional wrinkles. I'm ignoring those here to concentrate on the pointer aspect.)
Adding indirection adds complexity
When we do this sort of thing—pass a pointer value pointing to the original variable, where the original variable itself holds some initial or intermediate value and we update it as we go along—the function that receives this pointer value now has a variable of type pointer to T for some type T. This extra variable is a variable. That means we can assign a new value into it. If and when we do so, we lose the original value:
func g2(a *int) (bool, error) {
... section 1: do stuff with `*a` ...
var another int
a = &another
... section 2: do more stuff with `*a` ...
return ok, err
}
In section 1, a
points to, and *a
thus refers to, whatever variable some caller passed to g2
. Making changes to *a
will show up there. But in section 2, a
points to another
, and *a
is initially zero (because another
is zero). Making changes to *a
here won't show up in the caller.
You can, if you like, avoid using *a
directly:
func g2(a *int) (bool, error) {
b := *a
p := &b
var ok bool
// presumably there's a `var err error` or equivalent too
for attempts := 0; !ok && attempts < 5; attempts++ {
... do things ...
... if things are working well, set ok = true and set p = a ...
... update *p ...
}
return ok, err
}
Sometimes this is the sort of thing you want: if things aren't going well we carefully don't overwrite *a
by overwriting b
instead, but if things are going well we overwrite *a
by having p
point to a
in the "update *p" section. But it's tougher to reason about: make sure you're getting a very clear benefit before you resort to this sort of thing.
And of course, if we have a pointer to some variable stored in a variable, we can take a pointer to that variable too: i := 3; a := &i; pa := &a
. That gives us the opportunity to yet add another level of indirection, ppa := &pa
, which gives us another opportunity, and so on. It's turtles all the way down pointers all the way up, except at the very end where we must have some sort of final answer like i
.
1Pass-by-name is particularly tricky, and not very common; see What is "pass-by-name" and how does it work exactly? But it did lead to a great joke that Niklaus Wirth used to tell about himself, that some would say "Nick-louse Veert" and hence call him by name, and others would say "Nickle's Worth" and hence call him by value. (I think I heard this one second hand—I don't think he ever came to the U when I was there.)