Go Pointers Explained for Beginners [Practical Examples]


GO

Reviewer: Deepak Prasad

Introduction to Go Pointers

In simple terms , a pointer in Go is a variable that stores the memory address instead of value. The memory address can be of another value located in the computer. Since a pointer references a location in the computer memory, getting access to the value stored in that particular memory address is called  dereferencing the pointer.

In Go , pointers are used for efficiency because everything is passed by value. With the help of pointers , we can pass by reference. When passing by value is used,  we make a copy of the variable, put it in a new memory location. On the other hand, passing by reference means we will pass the memory location instead of the value.

 

Go pointer syntax

When talking about pointers in Go, two operators come come in play, asterisk(*) and ampersand(&). Asterisk (*) operator is also called the dereferencing operator that is used to:

  1. Declare a pointer variable
  2. Access values stored in an address

When declaring a pointer variable , you follow the variable name with the type that the pointer points to as seen below.

var username string = "Go developer"
var usernamePointer *string = &username

When accessing stored value in a pointer, place the * operator before the pointer variable(this is called dereferencing), and that will return the value the pointer points to as below.

fmt.Println(*usernamePointer)

The ampersand is used to get/return the memory address  of a variable. To make that work, place  & operator before a variable(username variable) that stores a value and assign that to a pointer variable(usernamePointer).

var usernamePointer *string = &username

Let us start writing some code, create a main.go file in your working directory.

Example

package main
 
import "fmt"
 
func main() {
   // Defining variables to store values of type string, int, boo, and float64
   var username string = "Go developer"
   var age int = 12
   var isAwesome = true
   var height = 5.5
 
   // Declaring pointers of types :string, int, bool and float64 using * operator
   // Pointer to a string
   var usernamePointer *string = &username
   // Pointer to an integer
   var agePointer *int = &age
   // Pointer to a boolean
   var isAwesomePointer *bool = &isAwesome
   // Pointer to a floating number
   var heightPointer *float64 = &height
 
 
   fmt.Printf("usernamePointer : %v ===> username value: %v \n", usernamePointer, *usernamePointer)
   fmt.Printf("agePointer: %v  ===> age value: %v \n", agePointer, *agePointer)
   fmt.Printf("isAwesomePointer: %v  ===> isAwesome value: %v \n", isAwesomePointer, *isAwesomePointer)
   fmt.Printf("heightPointer : %v  ===> height value: %v \n", heightPointer, *heightPointer)
}

Run the program with the following command

go run main.go

Output

usernamePointer : 0xc00010c210 ===> username value: Go developer 
agePointer: 0xc00012a000  ===> age value: 12 
isAwesomePointer: 0xc00012a008  ===> isAwesome value: true 
heightPointer : 0xc00012a010  ===> username value: 5.5

In the above example , pointersusernamePointer, agePointer, isAwesomePointer , and heightPointer are pointers to type string , integer, boolean andfloat64 respectively.

We have defined variables namely username, age, isAwesomeand heightand set them to values "Go developer",12, true and 5.5 . We then created their respective pointer variables and stored addresses of their respective variables by using the ampersand (&) operator.

 

How to change value stored at a go pointer

If you want to change the stored values that a pointer points to, * operator can be used for this function. Place * operator before your pointer variable and assign it a value of the pointer type.

Example

package main
 
import "fmt"
 
func main() {
   // Defining variables to store values of type string, int, boo, and float64
   var username string = "Go developer"
   var age int = 12
   var isAwesome = true
   var height = 5.5
 
   // Declaring pointers of types :string, int, bool and float64 using * operator
   // Pointer to a string
   var usernamePointer *string = &username
   // Pointer to an integer
   var agePointer *int = &age
   // Pointer to a boolean
   var isAwesomePointer *bool = &isAwesome
   // Pointer to a floating number
   var heightPointer *float64 = &height
 
   // Change value at a pointer
   *usernamePointer = "Go and Cloud developer"
   *agePointer = 22
   *isAwesomePointer = false
   *heightPointer = 5.6
 
   fmt.Printf("usernamePointer : %v ===> username value: %v \n", usernamePointer, *usernamePointer)
   fmt.Printf("agePointer: %v  ===> age value: %v \n", agePointer, *agePointer)
   fmt.Printf("isAwesomePointer: %v  ===> isAwesome value: %v \n", isAwesomePointer, *isAwesomePointer)
   fmt.Printf("heightPointer : %v  ===> height value: %v \n", heightPointer, *heightPointer)
}

Output

usernamePointer : 0xc000010250 ===> username value: Go and Cloud developer 
agePointer: 0xc0000140c8  ===> age value: 22 
isAwesomePointer: 0xc0000140e0  ===> isAwesome value: false 
heightPointer : 0xc0000140e8  ===> height value: 5.6

 

Nil Pointers

In Go, all variables of different types have their respective zero value. Pointers also have their nil value. When you define a pointer to a type, with no value assigned to the type, the pointer will be a nil. In other words, nil means that nothing has been initialized to the pointer yet.

Example

package main
 
import "fmt"
 
func main() {
   // Declaring pointers of types :string, using * operator with nil value
   // Pointer to a string
   var usernamePointer *string
  
   fmt.Printf("usernamePointer : %v ===> username value: %v \n", usernamePointer, *usernamePointer)
  }

Output

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47f549]
 
goroutine 1 [running]:
main.main()
        /home/Go/pointers/main.go:16 +0x49
exit status 2

In the above output, the program panics because we have declared pointers with nil value and we are trying to dereference the pointers to get access to their value which in essence does not exist (No value)

To solve this problem, we need if conditions to check if the pointer is nil

Example

package main
 
import "fmt"
 
func main() {
   // Declaring pointers of types :string with nil value
   // Pointer to a string
   var usernamePointer *string
 
   if usernamePointer == nil {
       fmt.Println("username pointer is nil")
       return
   }
   fmt.Printf("usernamePointer : %v ===> username value: %v \n", usernamePointer, *usernamePointer)
 
}

Output

username pointer is nil

 

Defining a pointer using the new() function

Another way to get a pointer in Go is by using the new() function. new() function takes a type as an argument, allocates memory to fit the value of that type and returns a pointer to it.

Example

package main
 
import "fmt"
 
func main() {
   // Declaring pointers of types :string with nil value
   // Pointer to a string
   usernamePointer := new(string)
 
   fmt.Printf("usernamePointer : %v ===> username value: %v \n", usernamePointer, *usernamePointer)
 
}

Output

usernamePointer : 0xc000010250 ===> username value:

One thing to note though is new() function returns a zeroed value.

 

Pointers with functions in Go

In Go, values are passed to function in two ways name:

  1. Pass by value
  2. Pass by reference

 

1. Pass by value

Passing by value means that a function takes the value of a variable as an argument. When a variable is passed by value its value can only be modified and used with the function scope where it was passed. Modifying this variable will not change its value outside the function scope.

In Go, all function arguments are passed by value. This means that when you pass a variable to a function in Go, a copy of the variable is created, and the function operates on the copy, not the original variable. Any changes made to the copied variable within the function will not affect the original variable outside of the function.

Example

func main() {
   // Pointer to a string
   username := "Go developer"
   // Trying to change username by passing its  actual value
   changeNameByValue(username)
   // Print the new username value
   fmt.Println(username)
}
 
func changeNameByValue(name string) {
   name = "Go, Python and cloud developer"
   fmt.Println(name)
}

Output

Go developer

 

2. Pass by reference

Pass by reference is a method of passing arguments to a function in which the function receives a reference (memory address) to the original variable, rather than a copy of the variable's value. When a function is called with pass by reference, it can directly access and modify the original data associated with the variable, affecting its value outside the function's scope.

In Go, all function arguments are passed by value, meaning that the function receives a copy of the variable's value. However, you can achieve pass-by-reference-like behavior in Go by using pointers. When you pass a pointer to a function, the function receives a copy of the pointer, which points to the same memory location as the original pointer. This allows the function to modify the original data.

In the function definition we add asterisk (*) before a type to denote that the function takes a pointer to the type.

Example

package main
 
import "fmt"
 
func main() {
   // Pointer to a string
   username := "Go developer"
   // Trying to change username by passing it a memory address using the & operator
   changeNameByReference(&username)
   // Print the new username value
   fmt.Println(username)
 
}
 
func changeNameByReference(name *string) {
   // name : Pointer to variable
   // dereference name variable using the * operator
   *name = "Go and cloud developer"
}

Output

Go developer
Go and cloud developer

Please note that changeNameByReference()function only accepts a pointer value and it changes the actual value of username in the code.

 

When to use a Pointer in Go

1. Use when you want to modify variables

In situations where you want to modify the value of a variable across program, pointers become handy. You only modify the variable  at one point of the program and the change will take place all over the program.

In this case we will be passing an address of where the data is held instead of passing the value of the data.

 

2. Use when working with  a large amount of data

A variable that stores a lot of data , say an array of users pulled from the database needs to be handled with care for performance and efficiency sake. Passing this variable by value all over the place in the program will make the program consume a lot of memory because more copies are made when using the variable. To solve this , we pass the pointer to the variable and we will have a single point of reference without making copies.

 

Go Methods Set

In Go , a type may have methods set associated with it. Method set of a type determines the interfaces that that type implements and the methods  that can be called using a receiver of that type.

When dealing with method set, some few rules must be followed namely:

  1. The method set of an interface type is its interface
  2. The methods set of any type T consist of all the methods declared with receiver type T.
  3. The method set of a corresponding pointer type *T is the set of all methods declared  with receiver *T or T.
  4. If you have a T and it is addressable you can call methods that have a receiver type of *T as well as methods that have a receiver type of T, because the method call t.Meth() will be equivalent to (&t).Meth()
  5. If you have a T and it isn't addressable (for instance, the result of a function call, or the result of indexing into a map), Go can't get a pointer to it, so you can only call methods that have a receiver type of T, not *T.

Example

package main

import "log"

type Animal interface {
	// The method set of an interface type is its interface
	Walk()
}

type Cat struct {
	name string
}

// A method of type T
func (c Cat) Walk() {
	log.Println("Animal name:", c.name)
}

// A method of type *T
func (c *Cat) changeName(newName string) {
	c.name = newName
}

func main() {
	c1 := &Cat{name: "Tom"} // Pointer type
	c2 := Cat{name: "Katie"}

	// c1 with pointer receiver : Has all methods attached to both *Car(pointer) and Cat(Non-pointer)
	c1.changeName("Tommy")
	// C1 has access to Walk() method although Walk() method expect a pointer receiver
	c1.Walk()
	// c2 non-pointer receiver: // c2 with value receiver : Has all methods attached to Cat(Non-pointer)
	c2.changeName("Kate")
	c2.Walk()
}

Output

2022/06/14 16:58:28 Animal name: Tommy
2022/06/14 16:58:28 Animal name: Kate

In the above example we define an Animal{}interface with a single method Walk(). Any type that wants to implement the Animal{} interface must have the Walk() method hence.

Using the above example, c2 is a value type and it should not have access to changeName()  method, because changeName() takes a pointer receiver (The methods set of c2 consist of all the methods declared with receiver type c2)

To get access to changeName(), c2 applies the 4th rule (If you have a T and it is addressable you can call methods that have a receiver type of *T as well as methods that have a receiver type of T, because the method call t.Meth()will be equivalent to (&t).Meth()

 

Summary

Go pointers are magical in that they allow us to get access to different memory addresses easily. Go pointers also allow us to change the value of a variable anywhere in our code by just using the pointer to that value. This feature comes with an advantage because we do not need to make copies of variables in order to change their value. This results in good memory management and fast execution speeds in our programs.

 

Antony Shikubu

Antony Shikubu

He is highly skilled software developer with expertise in Python, Golang, and AWS cloud services. Skilled in building scalable solutions, he specializes in Django, Flask, Pandas, and NumPy for web apps and data processing, ensuring robust and maintainable code for diverse projects. You can reach out to him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

2 thoughts on “Go Pointers Explained for Beginners [Practical Examples]”

  1. Passing by value is used when you want to change the original value of a variable across the application.
    This is in-correct

    Reply

Leave a Comment