Go Pointers for Advanced Developers: Advanced Techniques and Best Practices

Ambiyansyah Risyal
4 min readDec 24, 2022

--

Go Pointers for Advanced Developers: Advanced Techniques and Best Practices
Photo by Fern M. Lomibao on Unsplash

Go Pointers for Advanced Developers: Advanced Techniques and Best Practices

As an advanced Go developer, you’re no stranger to the power and versatility of pointers. These handy tools allow you to manipulate data and memory directly, giving you precise control over your code.

In this article, we’ll delve into some advanced techniques and best practices for working with pointers in Go. We’ll start by reviewing some of the fundamental concepts of pointers, then move on to more advanced topics like pointer arithmetic and indirections.

So, without further ado, let’s dive in!

What are pointers?

A pointer is a special type of variable that stores the memory address of another variable. In Go, pointers are denoted by the * symbol, followed by the type of the variable being pointed to. For example, the following code creates a pointer to an int:

var p *int

To assign a value to a pointer, you must first allocate memory for the value using the new function. For example:

p = new(int)
*p = 42

The first line of this code creates a new int value and stores the memory address in p. The second line assigns the value 42 to the int value pointed to by p.

Pointers can also be used to pass values to functions by reference, rather than by value. This can be useful when you want to modify a value in a function and have those changes reflected in the calling function. For example:

func increment(p *int) {
*p++
}

x := 42
increment(&x)
fmt.Println(x) // 43

In this code, the increment function takes a pointer to an int as an argument. The * operator is used to dereference the pointer and access the underlying value. The ++ operator increments the value by one, and the changes are reflected in the calling function.

Pointer arithmetic

One of the more advanced features of pointers is the ability to perform arithmetic on them. This can be useful when working with arrays or slices, as it allows you to easily access specific elements or iterate over a range of values.

For example, consider the following code:

a := [5]int{1, 2, 3, 4, 5}
p := &a[0]

for i := 0; i < len(a); i++ {
fmt.Println(*p)
p++
}

This code creates an array of int values and a pointer to the first element. The loop iterates over the elements of the array and prints each value using the * operator to dereference the pointer. The p++ operator increments the pointer by the size of an int value, allowing it to point to the next element in the array.

Pointer arithmetic can also be used to access elements of a slice or array using an offset from the beginning of the slice or array. For example:

a := [5]int{1, 2, 3, 4, 5}
p := &a[0]

fmt.Println(*(p + 2)) // 3

In this code, the pointer is incremented by the size of two int values, allowing it to point to the third element of the array.

Indirections

Another powerful feature of pointers is the ability to use them for indirections. This means that a pointer can point to another pointer, which in turn points to the underlying value.

Here’s an example of how indirections can be used:

var p1 *int
var p2 **int

x := 42
p1 = &x
p2 = &p1

fmt.Println(**p2) // 42

In this code, p1 is a pointer to an int, and p2 is a pointer to a pointer to an int. The value of x is assigned to p1, and the address of p1 is assigned to p2. The double * operator is used to dereference p2 and access the underlying value of x.

Indirections can be useful when you need to pass a pointer to a function that expects a pointer to a specific type, but you only have a pointer to a different type. For example:

type A struct {
Value int
}

type B struct {
Value *int
}

func foo(p *A) {
// do something with p
}

b := B{Value: new(int)}
*b.Value = 42

foo(&A{Value: *b.Value})

In this code, the foo function expects a pointer to an A struct as an argument. However, we only have a pointer to an int value in the b struct. To solve this problem, we can create a new A struct with the value of *b.Value and pass a pointer to this struct to the foo function.

Best practices for working with pointers

Here are a few best practices to keep in mind when working with pointers in Go:

  • Use pointers sparingly. While pointers can be useful in certain situations, they can also make your code more complex and harder to understand. Only use pointers when you really need them.
  • Be careful when using pointer arithmetic. It’s easy to make mistakes when working with pointers, especially when using arithmetic. Make sure to test your code thoroughly to ensure that it’s correct.
  • Use the & operator to get the address of a variable. This is the most straightforward way to create a pointer in Go.
  • Use the * operator to dereference a pointer. This is the most straightforward way to access the underlying value of a pointer.
  • Use the new function to allocate memory for a new value. This is the most straightforward way to create a new value and get a pointer to it in Go.

Conclusion

In this article, we’ve covered some advanced techniques and best practices for working with pointers in Go. We’ve looked at pointer arithmetic, indirections, and a few best practices to keep in mind when using pointers in your code.

While pointers can be a powerful tool in the right hands, they can also be tricky to work with. Make sure to use them sparingly and test your code thoroughly to ensure that it’s correct.

As always, happy coding!

--

--

Ambiyansyah Risyal
Ambiyansyah Risyal

Written by Ambiyansyah Risyal

Software engineer. Lover of learning and creating. Sharing thoughts and experiences on tech and software development. Always seeking new ideas and techniques.

Responses (2)