Pointers in Go

Rajesh GurajalaRajesh Gurajala
6 min read

Introduction to Pointers

As we have learned, a variable is a named location in memory that stores a value.

When we assign a value to a variable, we are storing that value in a specific location in memory.

We can manipulate the value of a variable by assigning a new value to it or by performing operations on it.

A Pointer is a variable

A pointer is a variable that stores the memory address of another variable. This means that a pointer "points to" the location of where the data is stored, not the actual data itself.

The * syntax defines a pointer and & operator generates a pointer to its operand.

var p *int
myString := "hello"
myStringPtr := &myString

Referencing

It's possible to define an empty pointer. For example, an empty pointer to an integer:

Its zero value is nil, which means it doesn't point to any memory address. Empty pointers are also called "nil pointers".

var p *int
fmt.Printf("value of p: %v\n", p)    // value of p: <nil>

Instead of starting with a nil pointer, it's common to use the & operator to get a pointer to its operand:

myString := "hello"      // myString is just a string
myStringPtr := &myString // myStringPtr is a pointer to myString's address
fmt.Printf("value of myStringPtr: %v\n", myStringPtr)    // 0x140c050

De Referencing

The * operator dereferences a pointer to get the original value.

*myStringPtr = "world"                              // set myString through the pointer
fmt.Printf("value of myString: %s\n", *myStringPtr) // read myString through the pointer

Unlike C, Go has no pointer arithmetic .

A Simple Example

Complete the removeProfanity function.

It should use the strings.ReplaceAll function to replace all instances of the following words in the input message with asterisks.

WordReplacement
fubb****
shiz****
witch*****

It should update the value in the pointer and return nothing.

package main

import (
    "strings"
    "fmt"
)

func removeProfanity(message *string) {
    *message = strings.ReplaceAll(*message, "fubb", "****")
    *message = strings.ReplaceAll(*message, "shiz", "****")
    *message = strings.ReplaceAll(*message, "witch", "*****")
}

func main() {
    msg := "you fubb witch shiz"
    removeProfanity(&msg)
    fmt.println(msg) // Output: you **** ***** ****
}

Nil Pointers

Pointers can be very dangerous.

If a pointer points to nothing (the zero value of the pointer type) then de referencing it will cause a runtime error (a panic) that crashes the program. Generally speaking, whenever you're dealing with pointers you should check if it's nil before trying to de reference it.

func removeProfanity(message *string) {
    if message == nil {
        return
    }
    *message = strings.ReplaceAll(*message, "fubb", "****")
    *message = strings.ReplaceAll(*message, "shiz", "****")
    *message = strings.ReplaceAll(*message, "witch", "*****")
}

Pass by Reference

Functions in Go generally pass variables by value, meaning that functions receive a copy of most non-composite types:

func increment(x int) {
    x++
    fmt.Println(x)
    // 6
}


func main() {
    x := 5
    increment(x)
    fmt.Println(x)
    // 5
}

The main function still prints 5 because the increment function received a copy of x.

One of the most common use cases for pointers in Go is to pass variables by reference, meaning that the function receives the address of the original variable, not a copy of the value. This allows the function to update the original variable's value.

func increment(x *int) {
    *x++
    fmt.Println(*x)
    // 6
}

func main() {
    x := 5
    increment(&x)
    fmt.Println(x)
    // 6
}

Fields of Pointers

When your function receives a pointer to a struct, you might try to access a field like this and encounter an error:

msgTotal := *analytics.MessagesTotal

Instead, access it – like you'd normally do — using a selector expression.

msgTotal := analytics.MessagesTotal

This approach is the recommended, simplest way to access struct fields in Go, and is shorthand for:

(*analytics).MessagesTotal

Example :

Write a get Message Text function. It should accept a pointer to an Analytics struct and a Message struct (not a pointer).

It should look at the Success field of the Message struct and, based on that, increment the Messages Total, Messages Succeeded, or Messages Failed fields of the Analytics struct as appropriate.

package main

type Analytics struct {
    MessagesTotal     int
    MessagesFailed    int
    MessagesSucceeded int
}

type Message struct {
    Recipient string
    Success   bool
}

func getMessageText(a *Analytics, m Message) {
    if !m.Success {
        a.MessagesFailed++
    } else {
        a.MessagesSucceeded++
    }
    a.MessagesTotal++
}

Pointer Receivers

A receiver type on a method can be a pointer.

Methods with pointer receivers can modify the value to which the receiver points. Since methods often need to modify their receiver, pointer receivers are more common than value receivers. However, methods with pointer receivers don't require that a pointer is used to call the method. The pointer will automatically be derived from the value.

Pointer Receiver

type car struct {
    color string
}

func (c *car) setColor(color string) {
    c.color = color
}

func main() {
    c := car{
        color: "white",
    }
    c.setColor("blue")
    fmt.Println(c.color)    // prints "blue"
}

Non Pointer Receiver

type car struct {
    color string
}

func (c car) setColor(color string) {
    c.color = color
}

func main() {
    c := car{
        color: "white",
    }
    c.setColor("blue")
    fmt.Println(c.color)    // prints "white"
}

Pointer Receiver Code

Methods with pointer receivers don't require that a pointer is used to call the method. The pointer will automatically be derived from the value.

type circle struct {
    x int
    y int
    radius int
}

func (c *circle) grow() {
    c.radius *= 2
}

func main() {
    c := circle{
        x: 1,
        y: 2,
        radius: 4,
    }

    // notice c is not a pointer in the calling function
    // but the method still gains access to a pointer to c
    c.grow()
    fmt.Println(c.radius)
    // prints 8
}

Pointer Performance

Occasionally, new Go developers hear "pointers don't pass copies" and take that to a logical extreme, concluding:

Pointers are always faster because copying is slow. I'll always use pointers!

No. Bad. Stop.

Here are my rules of thumb:

  1. First, worry about writing clear, correct, maintainable code.

  2. If you have a performance problem, fix it.

Before even thinking about using pointers to optimize your code, use pointers when you need a shared reference to a value; otherwise, just use values.

If you do have a performance problem, consider:

  1. Stack vs. Heap

  2. Copying

Interestingly, local non-pointer variables are generally faster to pass around than pointers because they're stored on the stack, which is faster to access than the heap. Even though copying is involved, the stack is so fast that it's no big deal.

Once the value becomes large enough that copying is the greater problem, it can be worth using a pointer to avoid copying. That value will probably go to the heap, so the gain from avoiding copying needs to be greater than the loss from moving to the heap.

One of the reasons Go programs tend to use less memory than Java and C# programs is that Go tends to allocate more on the stack.

0
Subscribe to my newsletter

Read articles from Rajesh Gurajala directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rajesh Gurajala
Rajesh Gurajala