An Introduction to Pointers for Go Programmers Not Coming from C Family Languages

As co-designer Rob Pike noted in 2012, much to his surprise, most Go programmers come from languages like Python and Ruby, not from C or C++ or the like. As such, they have trouble with one of the most important properties of all these languages, including Go: pointers. Very high-level languages like Python don’t usually have you worry about pointers, so it’s understandable that these new Go users would have trouble. Unfortunately, Go runs a lower level, and thus the relationship between memory and storage becomes very important.

This confusion usually manifests itself in the form of “what’s the difference between (t T) and (t *T) in method receivers?” asked on a near-daily basis in #go-nuts. I originally had a post planned talking about that specifically, but other people in #go-nuts argued that this is a problem of not understanding pointers, so I’ll start with this topic instead.

The good thing about Go that’ll make this post much easier to consume is that Go restricts what you can do with pointers quite heavily and lets you do a few things that’ll cause the neighbor’s cat to explode in any other language, so we won’t have to go into the murky waters of C pointer usage. So if you’re already crouching behind your chair in fear, don’t worry, this won’t hurt.

Thanks to buro9, Noeble, jescalan, bhenderson, and NHO in #go-nuts for input.

Data is stored in memory, memory is referenced by addresses, and a pointer is data that stores an address.

I… can’t really make that any less concise, so there’s the thesis statement as a topic header.

Let’s say you have

var x int16 = 5
var y float64 = 2.34

These two variables would be stored in your computer’s memory. Memory is stored in byte-sized cells, and each cell has a numerical identifier called an address. For example, a possible configuration of the above could be


memory

The exact in-memory representation of the values is not important, just that the data is stored in memory, has a definite size (you’ll notice that the number of bytes is based on the number at the end of the type name), and has a definite address.

The address of a variable will not change throughout the lifetime of that variable. Knowing this, you’d probably start wondering if there is a way to reference the address of that variable from other parts of the program, so as to change the value of the variable from an outside function, for instance. And you’d be right: that’s what a pointer does.

The expression

&x

returns the address of x. Simple as that, no? The type of this expression is the same as the type of x but with a * prefixed; in this case, *int16. The * in the type is read “pointer to”, just as [] is read “slice of”.

So now let’s create a pointer:

var p *int16 = &x

If you print the value of p, you should see some random number, possibly in hexadecimal. This number is the address in memory. Again, its exact value is not important, apart from not being equal to zero. We’ll get to that later.

Here’s a picture of what p and x look like in memory.


pointer

Notice that a pointer takes a very small amount of memory. This makes pointers really useful if you want to carry around large structures of data: instead of copying all that data back and forth, just pass around a pointer!

So how do you get the original value out of a pointer? Easy: just prefix the pointer with * again:

if *p != x {
    panic("shouldn't happen")
}

(Some of you might wonder why the * goes before, but things like [] and . go after. This was a historical design decision.)

You can use pointers to change variables indirectly as well:

*p = 20
fmt.Println(x) // will print 20

This last bit is very important: it’s how you can change variables passed as parameters to functions!

func changeFloat(f *float64) {
    *f = 987.65
}
changeFloat(&y)
fmt.Println(y) // prints 987.65

You can compare two pointers to see if they represent the same thing. The pointers must point to the same type, otherwise the compiler will complain:

if p != &x {
    panic("shouldn't happen")
}
if p != &y {
    panic("shouldn't compile")
}

The special keyword nil can be used to represent an uninitialized pointer; that is, a pointer that points nowhere. You cannot get the thing pointed to by a nil pointer, because it points nowhere. To be specific, the address value of a nil pointer is always zero, which is guaranteed to panic at runtime if you try to access the memory there.

You can use the special nil value as an indicator, however:

// DoSomething does something.
// If options is nil, default options are used instead.
func DoSomething(options *Options) {
    // load default options
    if options != nil {
        // load specified options
    }
    // do work
}

nil is a typeless expression; the compiler will infer the proper type out of the rest of the expression. Because nil is the zero value for a pointer,

var z *string

sets z to nil.

You don’t have to point to variables only either. The built-in function new(T) creates a new object of type T and returns a pointer to that new object.

var q = new(string)
*q = "hello, world"
fmt.Println(*q)

The object created by new() is initially given its type’s zero value.

That’s pretty much all you can do with (that is, once you have) pointers. Unlike C, Go doesn’t let you add pointers or subtract pointers or compare pointers using inequalities (that is, pointers in Go cannot be ordered). You can, however, use pointers as map keys; this might come in handy, so keep that in mind.

But Go does let you do two other special things involving pointers.

First, once you take the address of anything, that thing is kept alive as long as at least one pointer to that thing exists. It does not matter where, or what. It can be a local variable!

func NewThing() *Thing {
    var thing Thing
    // set thing's fields
    return &thing
}

This is completely legal in Go, and works as expected. In fact, you can even call this encouraged. Do this anywhere else and you’ll suddenly find that gravity now points in a Möbius strip shape or that fish can sing like Barry White or something.

Second, if you have a composite literal — one of those T{} things where T is a structure type or a slice type or a map type or a named version of one of those or something — then &T{} returns a pointer to that new composite literal instance. This isn’t just encouraged; it’s idiom:

address := &url.URL{
    Scheme: "http",
    Host:   "somewhere.com",
    Path:   resource,
}
fmt.Printf("%p\n", address) // explicitly prints the memory address of address

Finally, to access the fields of a pointer to a structure, you can just use . like you would with a normal struct:

if secure {
    address.Scheme = "https"
    address.Host += ":12345"
}

You can even omit the * when accessing an element in a pointer to an array or slice.

Wait, Pointers to Slices? (Okay, I Lied; There’s One Pitfall After All)

Some of you may have been wondering why you don’t need to use a pointer when passing a slice into a function:

func change(slice []int) {
    slice[4] = 4
}
j := []int{1, 2, 3, 4, 5}
change(j)
fmt.Println(j) // prints [1 2 3 4 4]

This is because a slice value is a reference to an underlying array, so changing an element actually changes the underlying array, which can be shared by multiple slices. Or in other words, slice values are pointers themselves! However, the slice variable is not a pointer. That is, you can’t change j, you can only change j‘s elements:

func change(slice []int) {
    slice = []int{6, 7, 8, 9, 10}
}
j := []int{1, 2, 3, 4, 5}
change(j)
fmt.Println(j) // prints [1 2 3 4 5]

This means you need to use a pointer to a slice if you want to change a slice’s length or capacity (such as with append()).

The same applies to maps and channels.

That’ll be all for this post. I won’t go into the unsafe package, which lets you break all the rules, but if you will ever venture into the world of the underlying system components like I have, you’re going to need it. And you’ll need to be really damn good at it, too.

3 thoughts on “An Introduction to Pointers for Go Programmers Not Coming from C Family Languages”

  1. I’ve read all the funny spam looking for a legit comment.
    Useful article by the way :) I especially appreciated the link to the old go blog post explaining the rationale for their declaration syntax.

  2. Woah! I’m really enjoying the template/theme of this website.
    It’s simple, yet effective. A lot of times it’s challenging to get that “perfect balance” between usability and appearance.
    I must say you’ve done a excellent job with this. Also, the blog loads very quick for me on Opera.
    Exceptional Blog!

  3. I’ve been browsing online more than 3 hours today, yet I never found any
    interesting article like yours. It is pretty worth enough for me.
    In my opinion, if all web owners and bloggers made good content
    as you did, the internet will be a lot more useful than ever before.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>