Pointers in Golang – 10+ Things You MUST Know

Pointers are one of the most important building blocks of writing good code. In this post, we are going to explore pointers, what they are, and how can they be used in Go to write excellent code.

1. What is a pointer?

A pointer is a variable that stores the address it points to. A pointer of a specific type can only point to that type.

2. Golang Pointer syntax

The syntax for the pointers is really simple. Here is the syntax of the declaration of pointers in Go.

var ptr *type
var ptrint *int     // pointer to an int

The zero-value of the pointer is nil.

3. Pointers Initialization in Go

The pointers of a type are initialized using the address-of(&) operator on a subject of that specific type. Here is the way to do it.

package main

import (
	"fmt"
)

func main() {
	var q int = 42
	var p *int     // declare the pointer
	p = &q         // initialize the pointer
	fmt.Println(p)  // 0x40e020
}

4. Go Pointers dereferencing

Dereferencing a pointer means getting the value inside the address the pointer holds. If we have a memory address, we can dereference the pointer to that memory address to get the value inside it. Here is the same example showing the dereference operation using the star(*) operator.

package main

import (
	"fmt"
)

func main() {
	var q int = 42
	var p *int
	p = &q
	fmt.Println(p)  // 0x40e020
	fmt.Println(*p) // 42
}

5. Pointer to pointer in Golang

A pointer variable can store even a pointers address since a pointer is also a variable just like others. So, we can point to a pointer and create levels of indirection. These levels of indirection can sometimes create unnecessary confusion so be wary when using it.

package main

import (
	"fmt"
)

func main() {	
	i := 64
	j := &i  // j is pointer to an int
	k := &j  // k is pointer to a pointer to an int (pointer to another pointer)
	
	fmt.Println(i)  // 64
	
	fmt.Println(j)  // 0x40e020 
	
	fmt.Println(*j) // 64 (value inside that address)
	
	fmt.Println(k)  // 0x40c138
	
	fmt.Println(*k) // 0x40e020 (address of j)
}

6. Pointer to interfaces

A pointer can point to anything even an interface. When an empty interface is used it returns nil as a value.

package main

import (
	"fmt"
)

func main() {	
	var a interface{}
	b := &a
	fmt.Println(b)    // 0x40c138
	fmt.Println(*b)   // <nil>
}

Here is an example using an interface with pointers.

package main

import (
	"fmt"
)

// declare interface
type Bird interface{
	fly()
}

type B struct{
	name string
}

// implement it
func (b B)fly() {
	fmt.Println("Flying...")
}

func main() {	
	var a Bird = B{"Peacock"}
	b := &a
	fmt.Println(b)    // 0x40c138
	fmt.Println(*b)   // {Peacock}
}

Here “a” is a struct of the type Bird which is then used for an interface type as you can see. This is polymorphism in action. Go allows polymorphism using interfaces. So, you can see pointers to a struct or an interface is an essential tool in Go.

7. Pointers as function arguments

Pointers can be used in function arguments just like value. It has some advantages over using values directly. It is a very efficient way to pass large objects to function. So, it is a great optimization to have it.

package main

import (
	"fmt"
)

// declare pointer as argument
func f(a *int) {
	fmt.Println(*a)
}

func main() {	
	var a int = 42
	
	// pass the address
	f(&a) // 42
}

Using large objects can slow down execution time, here is an example of passing a pointer to a struct. This is an efficient way to handle large objects.

package main

import (
	"fmt"
)

type Human struct {
	name string
	age int
	place string
}

func f(h *Human) {
	fmt.Println("The user", (*h).name, "is", (*h).age, "years old and he is from", (*h).place)
}

func main() {	
	john := Human{"John", 36, "Las Vegas"}
	
	f(&john) // The user John is 36 years old and he is from Las Vegas
}

Be careful when dereferencing a struct. If you use it like *structname.field1 then it will throw an error. The correct way to do it is (*structname).field1.

Using pointers inside functions makes the value mutable unless its const. So, whenever we want to change a value we should use a pointer to that as function argument and then do necessary modifications.

8. The “new” function in Go

The new function in Go returns a pointer to a type.

package main

import (
	"fmt"
)

func main() {	
	ptri := new(int)
	*ptri = 67
	
	fmt.Println(ptri)  // 0x40e020
	fmt.Println(*ptri) // 67
}

9. Returning a pointer from a function

Pointers of any type can be returned from a function just like any other value. It is really simple. Instead of returning the value directly we simply return the address of that value.

package main

import (
	"fmt"
)

func p() *int {  // specify return type as pointer
	v := 101
	
	// return the address
	return &v
}

func main() {	
	n := p()
	fmt.Println(n)  // 0x40e020
	fmt.Println(*n) // 101
}

10. Pointers to a function

Pointers to a function work implicitly in Go. That means we don’t need to declare it as a pointer.

package main

import (
	"fmt"
)

func main() {		
	f := func() {
		fmt.Println("a function")	
	}
	pf := f
	pf() // a function
}

11. Things to keep in mind while using Pointers in Go

Go doesn’t allow pointer arithmetic. So, we cannot do anything like unary increment or decrement as we can do in C/C++.

We may want to use a pointer to an array but there is a better option for it. And that’s slices. Slices are much more versatile than a pointer to an array. The code is concise and makes our lives a lot easier. So, use slices whenever possible.