Pointers in Go
This post is mostly targeting an audience of one: myself. It’s an attempt to write down what I’ve learned about pointers in Go since diving back into the language three months ago.
I wrote a little bit of C and C++ way back in the early 2000s. So, I have been exposed to pointers before–pretty sure I used them in a CS 101 class to try to impress my professor. That said, I’m willing to bet a paycheck my past self was using them without understanding much about them.
Pointers in Go seem much simpler than what I recall C/C++ pointers being.
Pointer Operators
There are two pointer operators:
&
before a variable says that the variable contains the memory address of the value (not the value itself)*
is the dereferencing operating, providing the actual value and not the memory address of the value
There is no pointer math in Go. I don’t remember the details of pointer math in C/C++, so I’m not going to dwell on it here.
Here is an example of using the pointer operators:
// Hello is a variable that holds a string value
Hello := "foobarbaz"
// hi is a variable that holds a pointer to a string,
// and we're assigning it the address of the Hello variable
var hi *string = &Hello
Pointer Receivers
Another, more subtle use of pointers in Go involves methods which use a pointer receiver
. I’m returning to Go
from Python, so it’s taking me time to get use to Go’s OOP concepts. A pointer receiver to a method looks like this:
type Foo struct {
bar string
}
func (f *Foo) Print() {
fmt.Println(f.bar)
}
func main() {
foo := Foo{bar: "whee!"}
foo.Print()
}
In the above example, Print
is a method on the Foo
type. This is a method
because it has a receiver
, which
is the (f *Foo)
bit that comes before the method itself. Here, the receiver is a pointer to Foo
. In this case, using
a pointer receiver doesn’t make much sense, because the Print()
method isn’t changing the Foo
type. It’s just
printing out the value of its bar
field. In this case, the above could be refactored to not use a pointer receiver:
type Foo struct {
bar string
}
func (f Foo) Print() {
fmt.Println(f.bar)
}
func main() {
foo := Foo{bar: "whee!"}
foo.Print()
}
It does make sense to use a pointer receiver when a method(s) updates the receiver.
type Foo struct {
bar string
}
func (f *Foo) Update(s string) {
f.bar = s
}
func (f *Foo) Print() {
fmt.Println(f.bar)
}
foo := Foo{bar: "whee!"}
foo.Print()
foo.Update("shazam!")
foo.Print()
> go run ./main.go
whee!
shazam!
Pointers as Function Arguments
Finally, function arguments can be pointers. By default, function arguments are passed by value. This means the function gets a copy of the value of the argument. This copy can be mutated within the scope of the function, but the original value is unaffected.
type Foo struct {
bar string
}
func (f *Foo) Print() {
fmt.Println(f.bar)
}
func update(f Foo) {
f.bar = "Hello, From Borat!"
}
func main() {
foo := Foo{bar: "Hello, world!"}
foo.Print()
update(foo)
foo.Print()
}
> go run ./main.go
Hello, world!
Hello, world!
To mutate a function argument, the argument should be passed by reference so the function will get a pointer to the memory address of the value.
type Foo struct {
bar string
}
func (f *Foo) Print() {
fmt.Println(f.bar)
}
func update(f *Foo) {
f.bar = "Hello, From Borat!"
}
func main() {
foo := Foo{bar: "Hello, world!"}
foo.Print()
update(&foo)
foo.Print()
}
> go run ./main.go
Hello, world!
Hello, From Borat!
Now what?
Now that I’ve explained pointers in Go to myself, I need to review a current project. I have a feeling I’m using pointers where it’s not necessary. In particular, I’m pretty sure I’ve used pointer receivers everywhere because I didn’t understand what I was doing.