What’s the point of GoLang Pointers? Everything you need to know!

Anna Peterson
6 min readNov 17, 2019

“Go has pointers. A pointer holds the memory address of a value.” BUT WHY?!

Even after reading the paragraph on pointers in A Tour of Go, the usefulness and implementation of pointers might seem unclear. In the following article, I will explain the point of pointers and you will emerge with both a theoretical and practical command of Go pointers! Both are important for mastering GoLang!

What Developers Get Wrong About Pointers

  • They are not specific. Developers might say, “Use a pointer” to mean similar but different things.
  • They call both &myVar and *myVar pointers, leaving newcomers confused.
  • They refer to an underlying value as the pointer itself.
  • They falsely believe that the zero value of a pointer is the zero value of the pointer’s type.

Unfortunately, this muddies the waters for newbies because *myVar is a pointer to myVar’s value and &myVar generates a pointer to the memory address of myVar.

Some developers use pointers pretty effectively, but they can’t explain their usage to peers. Or, as was my case, developers theoretically understand what pointers are, but don’t know when to use them. Wherever you’re at, I’ll get you up to speed!

So what are pointers, really?

A pointer is simply a variable containing the address of another variable.

Every variable has a location in memory. When we generate a pointer with the & operator, we are accessing the location where its operand is stored in memory. The * operator allows us to access the values held at a location in memory. Think of a book. The & operator would generate a page number, the * operator would tell you what’s on a given page number.

We could also think of a pointer as a trail that guides our program to a value. We get a signpost showing us where the trail is (*) and a signpost maker (&). I like this analogy because you can think of a little pointing arrow sign every time you see *T or creating an arrow sign every time you see &T.

Pointer signs photo courtesy of FreePik.com

Via A Tour of Go:

*:

The type *T is a pointer to a T value. Its zero value is nil.

&:

The & operator generates a pointer to its operand.

Try this code on the go playground or in your code editor to warm up(type it out yourself for best results! Don’t be lazy!):

package mainimport (
"fmt"
)
func noPointer() string {
return "string"
}
func pointerTest() *string {
return nil // You cannot return nil from a function that returns a string
// but you can return nil from a function that returns a pointer to a string!
// The zero value of a pointer is nil
}
func pointerTestTwo() *string {
s := "string" // &"string" doesn't work
return &s
}
func main() {
fmt.Println(noPointer()) // prints string
fmt.Println(pointerTest()) // prints <nil>
fmt.Println(pointerTestTwo()) // prints something like 0x40c158 (an address in memory)
s := pointerTestTwo() // now s holds an address in memory for a variable
fmt.Println(s) // something like 0x40c138
sp := *s // now sp holds the value found at the address s holds
fmt.Println(sp) // string}

Run this code

So, now we know what a pointer is and we even got to see what an address in memory looks like! Let’s dive a little deeper!

Why do we need pointers?

For those coming from languages like JavaScript, it can be difficult to understand why you would ever need a pointer. Don’t we always want to work with the underlying value and not the address where it’s held in memory? What are we going to do with that?

In the 1960s, pointers gave programmers a way to pass a variable’s memory location and then dereference it for manipulation as needed. This provided a way of creating more complex data structures while still consuming less memory. Harold Lawson is credited with inventing pointers. He wrote a seminal paper on the topic called, PL/I List Processing (see more resources at the end).

Instead of copying a large amount of data every time you need to pass it, programmers could pass its address. Going back to the book analogy, it’s easier to pass a function a page number than an entire page.

It’s not the 1960s anymore, but we’re still concerned with effective memory usage and efficiency. Go was written to be a very sleek language so it makes sense that it allows programmers to pass information this way. Still, GoLang could do this work behind the scenes and make it look like it isn’t, right?

In Go, everything is passed by value. Every time you pass something to a function, the function gets a value, but it doesn’t have access to the original object. See what I mean by typing the following code out in the Go playground!

package mainimport (
"fmt"
)
type User struct {
Name string
Pets []string
}
func (u User) newPet() {
u.Pets = append(u.Pets, "Lucy")
fmt.Println(u)
}
func main() {
u := User{Name: "Anna", Pets: []string{"Bailey"}}
u.newPet() // {Anna [Bailey Lucy]} -- the user with a new pet, Lucy!
fmt.Println(u) // Hey, wait! Where did Lucy go?
}

Run in the Go Playground

Here, I created a function to give a user a new pet, “Lucy” and I created a user, “Anna”.

Lucy was successfully appended to Anna’s pets in the newPet() function, but when we printed the user on the following line, she didn’t have her new pet!

I passed a value of a user to newPet() and gave her a new pet. We still haven’t changed (or mutated) the original user. If we go back to our code, we could write it like this:

func (u2 User) newPet() {
u2.Pets = append(u2.Pets, "Lucy")
fmt.Println(u2) // {Anna [Bailey Lucy]} -- user with a new pet, Lucy, but this user is not the same as the one in main()!
}
func main() {u := User{Name: "Anna", Pets: []string{"Bailey"}}
u.newPet()
fmt.Println(u) // The user still has one pet.
}

Now try this:

package mainimport (
"fmt"
)
type User struct {
Name string
Pet []string
}
func (p2 *User) newPet() {
fmt.Println(*p2, "underlying value of p2 before")
p2.Pet = append(p2.Pet, "Lucy")
fmt.Println(*p2, "underlying value of p2 after")
}
func main() {u := User{Name: "Anna", Pet: []string{"Bailey"}} // this time we'll generate a pointer!
fmt.Println(u, "u before")
p := &u // Let's make a pointer to u!p.newPet()
fmt.Println(u, "u after")
// Does Anna have a new pet now? Yes!
}

Run this code in the Go playground

With pointers, we’re passing a memory address of a user to newPet() and changing the user rather than a copy of the user.

Pointers provide shared access to a value. Pointers can help us avoid unintentionally changing data by being intentional about declaring shared access!

If someone asks you why pointers are used in Go, you can now tell them,

“Pointers are used for efficiency because everything in Go is passed by value so they let us pass an address where data is held instead of passing the data’s value, to avoid unintentionally changing data, and so we can access an actual value in another function and not just a copy of it when we want to mutate it.”

Some Usage Guidelines

What does this tell us about how to use pointers?

  • If we want a method to modify its receiver, we must use a pointer!
  • If we have a very large amount of data, we can pass the value of a reference to its location for better performance.
  • If we do not want to alter some data, the value of the receiver can be passed instead of a pointer to its receiver — as in our first example with newPet(). In this case, the receiver is treated more like an argument.
  • The main reason to use (or not use) pointers should be determined by access and how we want to use it!

Pointers: Concurrency

Pointers are not safe for concurrent access. When you use multiple threads, you will get inconsistent results if you are changing and reading data from the same places in memory. Be sure to limit access and pass values when necessary to reduce panics and unpredictable behavior. Avoid pointers in your Go routines.

Conclusions

I hope you enjoyed this discussion of GoLang pointers. If you have any questions or if I missed something, please let me know in the comments! Thanks!

More Resources

Pointers in GO

Pointers in Computer Science

--

--

Anna Peterson

As an Austin-based developer, my writing is focused on making tech more fun and approachable.