GO - Pointers

GO - Pointers

Welcome back to my Go series. I hope you are keeping your health 100%. That's nice! The awesome topic we are taking a look at today will be POINTERS. Go pointer is basically the go-to guy when you want to make your program more performance effective, in the case of passing large data around in your program, or you want to make changes to your composite data structure like slices, maps, struct e.t.c

This guy is so important that you will find yourself using it most of the time. So let's go and introduce ourselves to him and hopefully, we will like each other in the process. Without further ado, hop on the ride to Go pointer city...

Don't panic, I'm not Dominic Toretto from the Fast and Furious film. We are going to take things slow. But first, an introduction is important as the first step in making a friend

Introduction

Pointer is also a variable like every other variable you know or use. The only difference is that a pointer variable can not store any other data type except an address like this: 0xc00000a028

You see that's definitely not made for humans to read at all. Because pointers need to store memory addresses. There are two things you need to understand about pointers.

The first is an ampersand (&) - which when used to precede a variable name will return an address of that variable. The address will now be assigned to the pointer variable.

The next is an asterisk (*). This symbol does two things:

  • It will display the value in the location the pointer variable points to if the asterisk is used to precede the pointer variable name. The process is called dereferencing

  • The second use case is that it will create a pointer type for any of the data types you want to create and assign that type to the variable name.

Enough of the theories, let's see some codes!

Pointer - Variable Declaration

You won't be new to this if you are coming from C or C++. But I will assume we are all beginners here. The code that is shown below describes how a typical pointer declaration is done.

package main

import "fmt"

func main(){
    var y *int
    x := 78
    y := &x

    fmt.Print("%p, %p", y, &x)  // output the same address: 0xc0000aa058
}

The asterisk symbol that precedes the type int in the code above creates a pointer of type int. This in turn will make y a pointer variable. The next line of code assigns the value of 78 to the variable of x.

The ampersand that precedes variable x gets the address of x in memory and assigns it to the variable y

Nil pointer

All variables in Go have their respective zero values. This does not exempt the pointer variable. Whenever you declare a variable pointer without assigning a value to it, it will take up the zero value for a pointer - which is nil. The nil value indicates no value has been initialized or empty.

The declaration of the pointer variable below initially points to a nil value. This is as a result of the zero value in Go. You shouldn't try to dereference a nil pointer before the assignment. If you try to dereference it, the program will panic i.e throw an error.

package main

import "fmt"

func main(){
    var y *int
    x := 78
    y := &x

    fmt.Print("%p, %p", y, &x)  // output the same address: 0xc0000aa058
}

Pointer signifies mutability

There will come a time when you will need to make changes to some underlying data but without a pointer in place, you will not be able to. Did you ask why? Because every value you pass in Go is always copied. Let's take a look at the code below

package main

import "fmt"

func main(){
    x := 78
    changeFail(x) // output: 43

    fmt.Println(x)  // output: 78 
}

func changeFail(num int){
    num = 43
    fmt.Println(num) 
}

We create a variable of x and assigns 78 to it. The next thing we did was change the value assigned to another value of 43 in the calling function changeFail. The result was not quite what we expected. It failed. Here is why

When we passed in the x variable to the changeFail function. The function automatically made a copy of x and assigned the value to the num parameter which is a local variable inside the function. Therefore, the change we made to the num variable was not reflected in the original variable that we passed in.

The approach to solving this issue is passing the value by reference, and by reference, I mean pointer. You might ask, does using pointer change the way Go makes a copy of the value you want to pass around? The answer is NO

When you make use of your pointer, Go will still make a copy of that pointer value but remember the value is the address to another variable that holds the actual value in question. So let's have a look at how things will turn out.

package main

import "fmt"

func main(){
    x := 78
    changeFail(x) // output: 43
    changeSuccess(&x) // output: 54
    fmt.Println(x)  // output: 54 
}

func changeFail(num int){
    num = 43
    fmt.Println(num)
}

func changeSuccess(num *int){
    *num = 54
    fmt.Println(*num)
}

Isn't that beautiful? The changes we made here are:

Instead of passing the variable of x to the changeSuccess function directly, we passed in the address of its memory location which in turn was used to reassign and change its value from 78 to 54. There are many use cases of pointers but we will stop here for now because I don't want this to be a long read. We shall talk about it more in the future.

Thanks for taking the time to read up to this point. It's been a nice ride so far and I hope to see you in the next one. Peace out!