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.