Learn Go: Introduction to Pointers in Golang #22

In this tutorial, we are going to look at pointers in Go and how you can use them

Gopher

Lets go!!

Published · Feb 3 2020

Previous Tutorial: Marshal & Unmarshal JSON in Golang #21


In this tutorial, we are going to be covering pointers in Go and how you can use them within your own Go programs. We’ll be covering the best practices and we’ll be covering some of the most common use-cases for pointers.

By the end of this tutorial, you will have a solid understanding of pointers and how they can be used.
. . .

Introduction

When you write software in Go you’ll be writing functions and methods. You pass data to these functions as arguments. Sometimes, the function needs a local copy of the data, and you want the original to remain unchanged.

For example, if you’re a bank, and you have a function that shows the user the changes to their balance depending on the savings plan they choose, you don’t want to change the customer’s actual balance before they choose a plan; you just want to use it in calculations. This is called passing by value, because you’re sending the value of the variable to the function, but not the variable itself.

Other times, you may want the function to be able to alter the data in the original variable. For instance, when the bank customer makes a deposit to their account, you want the deposit function to be able to access the actual balance, not a copy.

In this case, you don’t need to send the actual data to the function; you just need to tell the function where the data is located in memory.

A data type called a pointer holds the memory address of the data, but not the data itself.

The memory address tells the function where to find the data, but not the value of the data. You can pass the pointer to the function instead of the data, and the function can then alter the original variable in place. This is called passing by reference, because the value of the variable isn’t passed to the function, just its location.
. . .

Defining Pointers

Let’s now take a step back and looks at the fundamentals of working with pointers.

We’ll start by looking at how we can define pointers within our Go code. In order to define a pointer, we can use the * symbol at the point at which we are declaring the variable and it will turn the variable into a pointer variable.
package main import "fmt" func main() { var age *int fmt.Println(age) fmt.Println(&age) }


When we attempt to run this? We should see the following:
<nil> 0xc00000e030

The first value represents the value of our pointer variable age. The second represents the address of this variable.
. . .

Initializing a Pointer

You can initialize a pointer with the memory address of another variable. The address of a variable can be retrieved using the & operator -
var x = 100 var p *int = &x

Notice how we use the & operator with the variable x to get its address, and then assign the address to the pointer p.

Just like any other variable in Golang, the type of a pointer variable is also inferred by the compiler. So you can omit the type declaration from the pointer p in the above example and write it like so -
var p = &x

Let’s see a complete example to make things more clear -
package main import "fmt" func main() { var a = 5.67 var p = &a fmt.Println("Value stored in variable a = ", a) fmt.Println("Address of variable a = ", &a) fmt.Println("Value stored in variable p = ", p) }
# Output Value stored in variable a = 5.67 Address of variable a = 0xc4200120a8 Value stored in variable p = 0xc4200120a8

. . .

Creating pointers using the new function

Go also provides a handy function new to create pointers. The new function takes a type as argument and returns a pointer to a newly allocated zero value of the type passed as argument.

The following example will make things more clear.
package main import ( "fmt" ) func main() { size := new(int) fmt.Printf("Size value is %d, type is %T, address is %v\n", *size, size, size) *size = 85 fmt.Println("New size value is", *size) }


In the above program, in line no. 8 we use the new function to create a pointer of type int. This function will return a pointer to a newly allocated zero value of the type int. The zero value of type int is 0. Hence size will be of type *int and will point to 0 i.e *size will be 0.

The above program will print
Size value is 0, type is *int, address is 0x414020 New size value is 85

. . .


Dereferencing a Pointer

You can use the * operator on a pointer to access the value stored in the variable that the pointer points to. This is called dereferencing or indirecting -
package main import "fmt" func main() { var a = 100 var p = &a fmt.Println("a = ", a) fmt.Println("p = ", p) fmt.Println("*p = ", *p) }
# Output a = 100 p = 0xc4200120a8 *p = 100

You can not only access the value of the pointed variable using * operator, but you can change it as well. The following example sets the value stored in the variable a through the pointer p -
package main

import "fmt"

func main() {
var a = 1000
var p = &a

fmt.Println("a (before) = ", a)

// Changing the value stored in the pointed variable through the pointer
*p = 2000

fmt.Println("a (after) = ", a)
}
# Output a (before) = 1000 a (after) = 2000

. . .

Passing pointer to a function

You can also pass the pointer to the function
package main import ( "fmt" ) func change(val *int) { *val = 55 } func main() { a := 58 fmt.Println("value of a before function call is",a) b := &a change(b) fmt.Println("value of a after function call is", a) }


In the above program, in line no. 14 we are passing the pointer variable b which holds the address of a to the function change. Inside change function, value of a is changed using dereference in line no 8. This program outputs,
value of a before function call is 58 value of a after function call is 55

. . .


Returning pointer from a function

It is perfectly legal for a function to return a pointer of a local variable. The Go compiler is intelligent enough and it will allocate this variable on the heap.
package main import ( "fmt" ) func hello() *int { i := 5 return &i } func main() { d := hello() fmt.Println("Value of d", *d) }


In line no. 9 of the program above, we return the address of the local variable i from the function hello.

The behavior of this code is undefined in programming languages such as C and C++ as the variable i goes out of scope once the function hello returns. But in the case of Go, the compiler does a escape analysis and allocates i on the heap as the address escapes the local scope.

Hence this program will work and it will print,
Value of d 5

. . .

Modify Pointer Array

Lets assume that we want to make some modifications to an array inside the function and the changes made to that array inside the function should be visible to the caller. One way of doing this is to pass a pointer to an array as an argument to the function.
package main import ( "fmt" ) func modify(arr *[3]int) { (*arr)[0] = 90 } func main() { a := [3]int{89, 90, 91} modify(&a) fmt.Println(a) }


In line no. 13 of the above program, we are passing the address of the array a to the modify function. In line no.8 in the modify function we are dereferencing arr and assigning 90 to the first element of the array. This program outputs [90 90 91]

a[x] is shorthand for (*a)[x]. So (*arr)[0] in the above program can be replaced by arr[0]. Lets rewrite the above program using this shorthand syntax.
package main import ( "fmt" ) func modify(arr *[3]int) { arr[0] = 90 } func main() { a := [3]int{89, 90, 91} modify(&a) fmt.Println(a) }


This program also outputs [90 90 91]
. . .

Nil Pointers

All variables in Go have a zero value. This is true even for a pointer. If you declare a pointer to a type, but assign no value, the zero value will be nil. nil is a way to say that nothing has been initialized for the variable.

In the following program, we are defining a pointer to a Creature type, but we are never instantiating that actual instance of a Creature and assigning the address of it to the creature pointer variable.

The value will be nil and we can’t reference any of the fields or methods that would be defined on the Creature type:
package main import "fmt" type Creature struct { Species string } func main() { var creature *Creature fmt.Printf("1) %+v\n", creature) changeCreature(creature) fmt.Printf("3) %+v\n", creature) } func changeCreature(creature *Creature) { creature.Species = "jellyfish" fmt.Printf("2) %+v\n", creature) }


The output looks like this:
Output 1) <nil> panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x109ac86]

When we run the program, it printed out the value of the creature variable, and the value is <nil>. We then call the changeCreature function, and when that function tries to set the value of the Species field, it panics. This is because there is no instance of the variable actually created. Because of this, the program has no where to actually store the value, so the program panics.

It is common in Go that if you are receiving an argument as a pointer, you check to see if it was nil or not before performing any operations on it to prevent the program from panicking.

This is a common approach for checking for nil:
if someVariable == nil { // print an error or return from the method or fuction }

Finally, if we create an instance of the Creature type and assign it to the creature variable, the program will now change the value as expected:
package main

import "fmt"

type Creature struct {
Species string
}

func main() {
var creature *Creature

fmt.Printf("1) %+v\n", creature)
changeCreature(creature)
fmt.Printf("3) %+v\n", creature)
}

func changeCreature(creature *Creature) {
if creature == nil {
fmt.Println("creature is nil")
return
}

creature.Species = "jellyfish"
fmt.Printf("2) %+v\n", creature)
}


Now that we have an instance of the Creature type, the program will run and we will get the following expected output:
Output 1) &{Species:shark} 2) &{Species:jellyfish} 3) &{Species:jellyfish}

When you are working with pointers, there is a potential for the program to panic. To avoid panicking, you should check to see if a pointer value is nil prior to trying to access any of the fields or methods defined on it.

Next, let’s look at how using pointers and values affects defining methods on a type.
. . .

Method Pointer Receivers

A receiver in go is the argument that is defined in a method declaration. Take a look at the following code:
type Creature struct { Species string } func (c Creature) String() string { return c.Species }

The receiver in this method is c Creature. It is stating that the instance of c is of type Creature and you will reference that type via that instance variable.

Just like the behavior of functions is different based on whether you send in an argument as a pointer or a value, methods also have different behavior. The big difference is that if you define a method with a value receiver, you are not able to make changes to the instance of that type that the method was defined on.

There will be times that you would like your method to be able to update the instance of the variable that you are using. To allow for this, you would want to make the receiver a pointer.

Let’s add a Reset method to our Creature type that will set the Species field to an empty string:
package main import "fmt" type Creature struct { Species string } func (c Creature) Reset() { c.Species = "" } func main() { var creature Creature = Creature{Species: "shark"} fmt.Printf("1) %+v\n", creature) creature.Reset() fmt.Printf("2) %+v\n", creature) }


If we run the program, we will get the following output:
Output 1) {Species:shark} 2) {Species:shark}

Notice that even though in the Reset method we set the value of Species to an empty string, that when we print out the value of our creature variable in the main function, the value is still set to shark.

This is because we defined the Reset method has having a value receiver. This means that the method will only have access to a copy of the creature variable.

If we want to be able to modify the instance of the creature variable in the methods, we need to define them as having a pointer receiver:
package main

import "fmt"

type Creature struct {
Species string
}

func (c *Creature) Reset() {
c.Species = ""
}

func main() {
var creature Creature = Creature{Species: "shark"}

fmt.Printf("1) %+v\n", creature)
creature.Reset()
fmt.Printf("2) %+v\n", creature)
}


Notice that we now added an asterisk (*) in front of the Creature type in when we defined the Reset method. This means that the instance of Creature that is passed to the Reset method is now a pointer, and as such when we make changes it will affect the original instance of that variables.
Output 1) {Species:shark} 2) {Species:}

The Reset method has now changed the value of the Species field.
. . .

Conclusion

Defining a function or method as a pass by value or pass by reference will affect what parts of your program are able to make changes to other parts.

Controlling when that variable can be changed will allow you to write more robust and predictable software. Now that you have learned about pointers, you can see how they are used in interfaces as well.
. . .
Next Tutorial: Defining Structs in Golang #23

. . .

On a mission to build Next-Gen Community Platform for Developers