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:
- Declare a pointer variable
- 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
, isAwesome
and height
and 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:
- Pass by value
- 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:
- The method set of an interface type is its interface
- The methods set of any type
T
consist of all the methods declared with receiver typeT
. - The method set of a corresponding pointer type
*T
is the set of all methods declared with receiver*T
orT
. - 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 ofT
, because the method callt.Meth()
will be equivalent to(&t).Meth()
- 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 ofT
, 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.
Passing by value is used when you want to change the original value of a variable across the application.
This is in-correct
Thank you for highlighting this. I have updated the section.