A One-Pager to Understanding Pointers in Go (expanded)
Gospel Lekia
Posted on January 2, 2024
Scared of pointers let鈥檚 end the fear once and for all.
Pointers are a powerful feature in programming languages that allow you to work with memory addresses directly. In the Go programming language, pointers work similarly to other C-style languages, but with some differences in syntax and behaviour.
To understand pointers, you'll need to clearly understand the following:
- variable
- value
- type
Let's define them in one statement: topic := "go programming"
.
The variable is topic
, the value is "go programming" and the type is a string. There is one more thing that is not obvious on the list and that's the memory address of the variable. Every variable has an address in memory and that's where pointers come in.
A variable does not only hold a value but also has an address. This is a hot fact to keep in mind. Consider it like a house. It has an address and can contain a person assuming one person per house.
Say Mr John Doe's house address is 100 ABC Street, Omo Island, Dukana. It means if we get to the above address we'll find Mr John.
Type is important in Go just like other languages. Every value in Go is of a type. For instance, 4, 9, and 5 can be called integer types, 5.0, and 4.4 float64 types, "welcome", and "tomorrow" string types and more. If a variable holds a value of a type then that variable is of that type.
age := 30
Variable, age here is an integer type since it holds an integer value.
There is also a pointer type just like every other type mentioned above.
Every variable has a location in memory (more like an address).
Let's say the address of a variable, b
in memory is 0xc000012028 (yes, memory addresses look like this). We can assign the value to another variable like so u := 0xc000012028
(note, this is an illustration). In this case u
is a pointer (to b
).
Exercise:
Mention the variable types in the examples.
name := "John Doe" // name is string type
age := 30 // age is integer type
height := 1.7 // height is float64 type
pToName := &name // ptoName is a pointer type; '&name' is the memory address of name
What is a Pointer in Go?
A pointer holds the memory address of a value or from the angle of a variable, a pointer is a variable that holds the memory address of another variable.
A pointer is represented by the *
symbol followed by the type of variable it points to. For example, the following code declares a pointer to an integer variable:
var p *int
In Go you declare a variable like this
var age int
but if age is a pointer you will declare it like this insteadvar age *int
. This is not the same as dereferencing (defined below).
Here, p is a pointer to an integer variable. It's important to note that p is currently a nil pointer, meaning it doesn't point to any valid memory address as it's just a declaration.
Creating a Pointer
To create a pointer to a variable, you use the &
operator followed by the variable's name. For example, the following code declares an integer variable x and creates a pointer p that points to it:
x := 42
p := &x // p points to x
Here, p
points to the memory address of x
, which is the address where the value 42 is stored.
Dereferencing a Pointer
There is a relationship between pointers and values.
Given an address, we can get the value at the location and vice-versa.
To access the value at the memory address, you use the * operator followed by the pointer variable name. This operation is called dereferencing the pointer. For example, the following code dereferences the pointer p and assigns the value 100 to the variable it points to:
*p = 100
// We're setting x to 100 through p.
// Now, if we print the value of x, it will be 100:
// x was initially 42 (defined above).
fmt.Println(x) // Output: 100
Note that you can only dereference a non-nil pointer. If you try to dereference a nil pointer, your program will crash with a runtime error.
Two Ways to Use the *
Symbol
To clear a common confusion around *
, it's good to know we can use it in two different ways.
- To indicate that a variable is a pointer in variable declaration.
- To deference a pointer variable.
The syntax examples in the first case: var age *int
, var name *string
. The *
is followed by the type.
In the second case, the *
is followed by a variable like so: *name
, *age
.
Passing Pointers to Functions
Normally, when a variable is passed to a function it's passed as a copy.
Modifying the variable in the function does not modify the one outside the function.
But if we want to modify the variable outside the function within the function we can pass it as reference using a pointer.
One common use of pointers is to pass a variable to a function by reference, allowing the function to modify the variable directly. To do this in Go, you simply pass the pointer to the function. For example, the following code declares a function that takes a pointer to an integer variable and increments its value:
func increment(p *int) {
*p++
}
To call this function and increment the value of x defined earlier, you pass the pointer to x as an argument:
increment(&x)
fmt.Println(x) // Output: 101
Here, increment(&x)
takes a pointer to x, which dereferences it and increments the value it points to.
Further Example
package main
import "fmt"
func main() {
age := 8 // declare a variable and assign a value to it.
printAge(age) // we pass a copy of the variable to the function.
// OUTPUT:
// AGE in function: 10
// memory location in function: 0xc000012040
fmt.Println("AGE in global:", age)
fmt.Println("memory location of age in global:", &age)
// OUTPUT:
// AGE in global: 8
// memory location of age in global: 0xc000012028
printAgePointerParameter(&age)
fmt.Println("AGE in global after pointer function:", age)
// OUTPUT:
// memory location in pointer function: 0xc000012028
// value in memory location in pointer function: 8
// age in pointer function after reasignment: 20
// AGE in global after pointer function: 20 (the original variable is affected after it's changed in the pointer function)
}
func printAge(age int) {
age = 10 // reasign a value to the variable
fmt.Println("AGE in function:", age)
fmt.Println("memory location in function:", &age)
}
func printAgePointerParameter(age *int) { // function takes a pointer type of int
fmt.Println("memory location in pointer function:", age)
fmt.Println("value in memory location in pointer function:", *age) // we deference the pointer and print it.
*age = 20 // we deference the variable and reasign a new value to it. This also affects the original variable.
fmt.Println("age in pointer function after reasignment:", *age)
}
Benefits of pointers
Dynamic Memory Allocation: Pointers are often used to allocate and manage memory dynamically in Go programs. This allows you to allocate memory for data structures at runtime, rather than relying on compile-time allocation. For example, you can use pointers to create linked lists, trees, and other dynamic data structures.
Sharing Data between Functions: Pointers can be used to share data between functions and avoid creating copies of large data structures. By passing a pointer to a function, you can allow the function to modify the original data structure directly, rather than creating a new copy of it.
Performance Optimization: Pointers can be used to optimize performance in certain situations, such as when working with large data sets or in high-performance computing applications. By working directly with memory addresses, you can avoid unnecessary memory copies and improve the efficiency of your program.
Interfacing with C Libraries: Go programs can interface with C libraries using pointers, which are a common feature in C programming. This allows you to take advantage of existing C libraries and leverage their functionality in your Go programs.
Conclusion
Pointers are a powerful tool in the Go programming language that allows you to work with memory addresses directly. They can be used to pass variables by 'reference' to functions, modify values directly, and work with complex data structures. However, it鈥檚 important to use pointers with care and avoid common pitfalls such as dereferencing nil pointers or using dangling pointers that point to invalid memory addresses.
Here is my personal Github gist(note) that inspires this article. Feel free to leave comments. https://gist.github.com/Yigaue/d0a0128a86914ef718ed48457d003031
Other Useful Resources
Go Curated learning resources
Go Address Operators (Go doc)
Pointer Types Operators (Go doc)
Pointers to a struct (Playground)
Pointers (playground)
Posted on January 2, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.