For beginners: Golang pointers make sense when used in pointer receivers

If you know how pointers work in pointer receivers, go away! Or, read on anyway and please, please tell me if I'm wrong on any point.

If you've studied a bunch of tutorials and examples already and you're still asking: what are pointers for? Welcome!

Background

We're usually told something like this: "Methods with pointer receivers can modify the value to which the receiver points [link]." Or this: pointers let you share data between functions.

If you're like me, this doesn't resonate in the clearest way. Part of the problem is the vocabulary. I don't have a computer science background. I didn't spend 4 years bandying terminology around so that it became second nature. I also went through an odious schooling system that still causes me to freeze up every time a sentence with one too many terms pops up.

So, let's take it a little bit slowly.

What this is about

I don't talk about value receivers. This whole thing is just to point out more pointedly to newcomers how pointers make sense when they're used in pointer receivers.

Sometimes you just need to see a slightly larger picture to understand.

Pointers

You've seen this example a thousand times:

a := 1
b := &a
fmt.Println(&a) // Prints 0xc000056058
fmt.Println(b) // Prints 0xc000056058 (same as above)

You know that pointers use the ampersand & to point to the memory address of a variable, as seen above. You also know that the asterisk * lets you take a look at the value of that variable:

fmt.Println(*b) // Prints 1

What's less well known at this stage in your career is that the asterisk * gives us full access to the value in that variable. You don't have to just look at it. You can change it if we like:

*b = 2
fmt.Println(a) // Prints 2. a has been changed to 2!

Now what?

Apply it to functions

Pointers are commonly used in methods, also known as functions. You probably also know by now that structs can have their own methods. So here's the example we'll use:

type letters struct {
    a int
}

func (l letters) add() { // This method belongs to the letters struct.
    fmt.Println(&l.a) // Prints 0xc000056078
    l.a++
}

func main() {

    // Instantiate an instance of the struct.
    l := letters{

    a: 1,
  }

    fmt.Println(&l.a) // Prints 0xc000056058. Different from in add()!

    l.add()

    fmt.Println(l.a)

}

This example has a struct with a single field a, which is of type int. The struct's method simply adds 1 to a.

Inside both main() and add(), we have fmt.Println(&l.a). This allows us to see the memory address of l.a. If we run this example, we'll see that l.a occupies different memory addresses in main() and in add(). This tells us that the instance of l.a in add() is a copy of the instance we made in main(). There are not the same instance.

That's why the last line fmt.Println(l.a) prints 1 and not 2.

Here is where the pointer comes in.

Pointer Receivers

The struct method is called a receiver. It "receives" the letters struct, whatever that's supposed to mean. I don't know how to explain the logic of this term because it doesn't really make sense to me. Anyway, we turn this method into a pointer receiver by adding an asterisk to its function signature, like so:

func (l *letters) add() { // Note the asterisk * before letters.
    fmt.Println(&l.a) // Prints 0xc000056058
    l.a++
}

That's it. With this little asterisk * in place, the memory address in both main() and add() become the same. That is, we're now pointing to the same instance of l.a. Running this script will print a 2 at the very end, showing that we've successfully accessed and changed the value of the struct instance.

Essentially, we're doing this, from the example at the beginning of this post:

*b = 2

But we're applying it to a function now. We're saying, using this asterisk *, let us fully access the values of this struct so that we may change them if we want to.

That's pretty much it on a basic level, I guess.

If the leap from pointer to pointer receiver confused you before, know that it's natural. Golang is a language. We get used to it with practice.

Want more?

If you want to dig deeper, here are some resources that explain other aspects of pointers and pointer receivers:

Golang code review comments on the receiver type (recommended read)

What is the difference between passing a struct and pointer of the struct, are they not both pointers? (I like this as a refresher for myself)

An example that discusses how pointers work on the memory stack (nice to keep in mind)

There is no pass-by-reference in Go (recommended read)

Go: Are pointers a performance optimization?

Beware of forgetting to pass a pointer object when using json.Marshal

Comments

There are currently no comments

New Comment

required

required (not published)

optional

required