Skip to main content

Demystifying Pointers in Go

Posted on January 22, 2019 by KeithThompsonKeithThompson

If you’ve never worked with a language that exposes pointers, it could be a little confusing. But the good news is pointers don’t need to be scary. In fact, pointers can be pretty straightforward. Here are the basics of pointers in Go:

Pointers vs. Values

One of the most common stumbling blocks for working with pointers is deciding if and when to use them. There are a few questions we can ask to help us decide:

  • Are we working with a built-in data type (string, int, maps, slices, etc.)? If so, we’ll normally want to use value types.
  • Do we want a function/method to make changes to a variable’s value? If so, we’ll want to define it to receive a pointer.
  • Is the variable holding a lot of information (e.g., a large array)? If so, we might want to consider defining functions and methods that receive pointers instead of values.

Let’s take a look at how this works in practice.

Understanding Method Receivers

When we define methods on structs, we need to specify the type of the receiver, which can be either a value type or a pointer type. To see how this works, let’s take a simple example User struct and a method called IncAge and change the definitions to see how the program changes. Here’s our starting point, where we’re only working with value types:

package main
import "fmt"
// User is just a simple struct
type User struct {
    Name string
    Age  int
}
// IncAge increases the User's age
func (u User) IncAge() {
    u.Age = u.Age + 1
}
func main() {
    kevin := User{Name: "Kevin Bacon", Age: 60}
    kevin.IncAge()
    fmt.Println(kevin.Age)
}

When we run this, we’ll see an output of 60. This is because when we define a method on a value receiver, the entire struct will be copied into the context of the method, and no modifications will stick to the receiver. Let’s change the method receiver to a pointer instead:

// IncAge increases the User's age
func (u *User) IncAge() {
    u.Age = u.Age + 1
}

When we run the program again, we can see that the age that is printed is 61. Since u is a pointer now, we’re going to be modifying the receiver itself instead of a copy of it. There are some benefits to defining methods on pointers instead of values:

  • There is no copying, so no extra memory needs to be allocated. This is especially good for structs with many fields or large collections that would be more costly to copy.
  • The receiver can be modified.

One of the drawbacks to using a pointer as the method receiver is that the method is potentially no longer safe for multi-threaded programming because multiple threads could potentially modify the receiver.

Initializing Struct Variables

The last thing that we’re going to look at regarding value types versus pointers is the difference between these two lines:

  1. kevin := User{Name: "Kevin Bacon", Age: 60}
  2. kevin := &User{Name: "Kevin Bacon", Age: 60}

With option one, kevin is a User, but in option two, he would be of the type *User. When working with structs, we’ll most likely see option two because there’s almost no reason not to use it. Go will automatically dereference the pointer if we define methods on the value type, so there isn’t much of an issue with using the pointer variable. If we change our example code to initialize a struct pointer, we won’t notice any difference.

Conclusion

Pointers have a reputation for being confusing, but hopefully, this post has made the topic more approachable. If you’d like to learn more about using Go, check out the new System Tooling with Go course!

0 Comments

Leave a Reply

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