Many people are scared of pointers when they first get started in Go, especially if they’re coming to Go from dynamic languages that don’t expose the concept. Even after they’ve gotten a good grasp on what pointers are and how they work, many new gophers feel a bit anxious about when they should be defining a method on a value rather than a pointer.
The Go team has given a good set of guidelines in their FAQ, listing three basic considerations.
- Modification: If you need to modify the receiver, then it must be a pointer.
- Efficiency: If your value is large, use a pointer.
- Consistency: Don’t mix pointer / value receivers for a given type.
There are exceptions, but starting with these guidelines as a rule of thumb will get you pretty far.
Another excellent resource when it comes to making the decision about pointer vs value receivers is the Code Review Comments wiki page on the golang/go project on GitHub. It has a section about receiver types, which you should absolutely go read. It concludes with when in doubt, use a pointer receiver.
Why Would You Ever Use a Non-Pointer Receiver?
When the receiver is a map or a slice you will almost always want to use a value receiver for these. Almost. Maps and slices act as references. Go Slices: Usage and Internals on the official Go blog is a fantastic deep dive into slices, and you should go read it if you haven’t already, in order to understand why you occasionally will want to use a pointer receiver for a slice.
Aside from types that act as references, the most common case for defining a value receiver rather than a pointer receiver is when you are defining a small struct type that acts as a value. The term value here might be a bit confusing, because this “value” is not the same “value” as in value receiver vs. pointer receiver.
This is about identity.
If you have two values of a given type, and all their fields have the same values in them, do they represent the same thing?
Sometimes the answer is yes. For example, 2:15pm is 2:15pm, provided that they’re both in the same timezone. You might have a bunch of copies of this value, each copy might live in a different place in memory, but they’re all equivalent.
This isn’t always the case. For example you could have two people, both named Bob Smith, and both born on the same day. That doesn’t mean that they’re the same person, as anyone married to a Bob Smith can attest to. You can’t just trade one Bob Smith for another.
Dates, times, points, vectors, web-safe colors are all typically small, and their identity is based on their value.
It’s idiomatic in Go to define a value receiver rather than a pointer receiver for these types.
Some people ask: Isn’t it always more efficient to use a pointer receiver?
- You profiled, right?
A pointer receiver introduces indirection. In some cases this indirection has a measurable cost.
> The problem most people have, is that they try to make this decision based on what they think the performance tradeoff will be. – William Kennedy, Using Pointers in Go
It’s far better to make choices based on what is simple and idiomatic, than on some pre-conceived idea of what will perform well.
To quote the Rules of Optimization:
- The First Rule of Optimization: Don’t.
- The Second Rule of Optimization: Don’t… yet.
- Profile before optimizing.