DEV Community

Cover image for A One-Pager to Understanding Pointers in Go (expanded)
Gospel Lekia
Gospel Lekia

Posted on

A One-Pager to Understanding Pointers in Go (expanded)

Scared of pointers let’s 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:

  1. variable
  2. value
  3. 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.

  1. name := "John Doe" // name is string type
  2. age := 30 // age is integer type
  3. height := 1.7 // height is float64 type
  4. 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 instead var 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

  1. To indicate that a variable is a pointer in variable declaration.
  2. 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++
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Run code in playground

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’s 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)

Top comments (0)