cover image for article

Golang: Pointers

#go

Introduction

In the tenth part of the series, we will be looking into an interesting concept in programming i.e. Pointer. It's a simple thing but a really powerful concept. Using pointers we can do several things very easily rather than writing a lot of code for a simple thing. We will be looking into basic concepts like declaration, referencing, de-referencing, and some examples on passing by reference, along with a pointer to struct instances.

Pointers

Pointers are simple, it's just their use case that makes it a big concept. Pointers are really powerful, they can do a lot of things that might seem impossible for a given problem. A pointer is a variable but unlike another variable which stores values in the form of integers, string, boolean, etc. pointers store the memory address. Memory address can be any valid location in memory that generally holds a variable.

So, using pointers we can play with the memory address of variables and modify the contents of the variable directly using the memory address rather than accessing the variable. In golang, we have ways to store pointers and perform operations for the same.

Declaring Pointers

To declare pointers in golang, we can use the * before the type of data type we want to refer to. This means a pointer needs to specify which data type it is referencing as a measure of caution to mismatch types in the variable. Initially, the pointer variable is mapped to <nil> that is it points to nothing but a null pointer.

package main

import "fmt"

func main() {
    var ptr *int
    fmt.Println(ptr)
}
$ go run pointer.go
<nil>

As we can see, the pointer that references an integer is initialized to nil. We have used * before the data type, this can be anything like *string , *bool , *float64 , etc.

The * and & in Pointers

After declaring a pointer, we can now move into assigning a pointer a memory address. Using the & or the ampersand operator we can get the memory address of a variable.

var n := 34
var a_pointer *int = &n
fmt.Println(a_pointer)
$ go run pointer.go
0xc0000a6080

Here, we can see that the pointer variable is storing the memory address of an integer variable. Don't worry about the value of the pointer variable, it is just a memory location on your machine. So, we use the & to access the memory address of any variable.

We have seen that the * is used to declare a pointer variable, but it is also used for dereferencing a pointer. So, if we used & to get the memory address of a variable, similarly we can use the * to get back the value from the memory address. Both are opposite in terms of accessing the value.

n := 34
var a_pointer *int = &n
fmt.Println(a_pointer)
m := *a_pointer
fmt.Println(m)
$ go run pointer.go
0xc0000a8080
34

As we can see, we have accessed the value stored in the pointer variable( a_pointer ) by using the * . Here, the variable which we have created m will be of type whatever is stored in the memory address of the provided pointer variable. In this case, it is int , it can anything.

So, this is how * and the & work in Golang. The * is used for declaring pointer variables as well as de-referencing pointer variables, and the & operator is used for accessing the memory address of the variable.

That's basically the concept of pointers in golang. It's that simple. Using the simple concept of referencing and de-referencing, we can perform some operations like passing by reference to functions which will allow us to actually pass the value rather than the copy of the variable's value.

Passing by Reference to Function

Now we have the fundamentals of pointers cleared, we can move into actually using them to do some really useful operations. Generally, when we use parameters such as integers, strings, bool, etc. we are passing the copy of the variables into the function rather than the actual value of the variable. This is where pointers come in. By using pointers to pass the memory address of the variables we need to pass in we actually pass the location of the variables.

Let's take a look at a simple example of a function that swaps the value of two variables.

package main

import "fmt"

func swap(x *int, y *int) {
    temp := *x
    *x = *y
    *y = temp
}

func main() {

    x := 3
    y := 6
    k := &x
    p := &y
    fmt.Printf("Before swapping : x = %d and y = %d.\n", x, y)
    swap(k, p)
    fmt.Printf("After swapping  : x = %d and y = %d.\n", x, y)
}
$ go run pointer.go
Before swapping : x = 3 and y = 6.
After swapping  : x = 6 and y = 3.

We can see here, that we have used pointers to pass the value of parameters to a function. Without using pointers, the value of the variable is passed as a copy but by using pointers, we are actually passing the memory address. In the main function, we first store the memory address of two variables x and y into two different pointer variables. We now can construct a function that accepts two memory addresses and perform further operations.

Inside the function, we have de-referenced the pointer variables as with * . Don't confuse x *int with *x . We use x *int to make the function realize that we are passing a pointer variable of an integer value, and *x is used to de-reference the memory address which is stored in x .

So, simply we - store the value in the memory location stored at x in the temp variable - store the value at the memory address stored in y into the memory address x . - store the value of the temp variable into the memory address stored in x .

We have successfully swapped two values without returning any values from the function.

Pointer to a Struct Instance/Object

We can now even modify the values of Struct objects/instances by referencing the instance to a pointer. By assigning the pointer variable to a struct instance, we have access to its associated properties and function. Thereby we can modify the contents directly from the pointer variable.

Let's take a look at a basic example of modifying properties using a pointer to a struct instance.

package main

import "fmt"

type Book struct {
    pages int
    genre string
    title string
}

func main() {
    new_book := Book{120, "fiction", "Harry Potter"}
    fmt.Println(new_book)
    fmt.Printf("Type of new_book -> %T\n", new_book)
    book_ptr := &new_book
    book_ptr.title = "Games of Thrones"
    fmt.Println(new_book)
}
$ go run pointer.go
{120 fiction Harry Potter}
Type of new_book -> main.Book
{120 fiction Games of Thrones}

So, we have created a pointer variable of the type which is a struct Book , this gives us access to the memory addresses associated with various properties defined in the struct. Using the pointer variable, we can access properties and thereby change the value directly as we have the memory address stored in book_ptr . So, if we say book_ptr.title = "Games of Thrones" , we are storing the string directly into the memory address of the new_book object as book_ptr refers to the memory addresses to the struct object new_book .

Here, we have literally changed the value of a property in a struct object using pointers. This is really powerful and time-saving. If pointers were not a thing, you would have to write a separate function for doing the same.

That's it from this part. Reference for all the code examples and commands can be found in the 100 days of Golang GitHub repository.

Conclusion

So, that's it we have covered enough basics of pointers so that we are able to understand the working of simple scripts or programs. Even simple data structures like slices or strings can be understood by using pointers in golang. From this part of the series, we were able to understand the declaration, referencing, and de-referencing of pointers along with passing by reference to functions and creating pointers to struct instances.

Thank you for reading. If you have any questions or feedback, please let me know in the comments or on social handles. Happy Coding :)