punteros-go-referencias-desreferencias

Pointers in Go: references, dereferences, and nil

  • 4 min

A pointer in Go is a value that stores the memory address of another value. It serves to share or modify data without copying them entirely.

If you come from languages like JavaScript or Python, the word “Pointer” might sound low-level, like weird addresses and things you usually don’t want to look at closely. If you come from C or C++, you probably have nightmares about segmentation faults and memory arithmetic.

I have good news: Pointers in Go are different. Go has taken the power of C pointers (the ability to access memory directly) but removed the dangerous part (pointer arithmetic).

The result is a system that allows us to write very efficient code without shooting ourselves in the foot.

Let’s understand what they are, what they are for, and how to use the & and * operators.

What exactly is a pointer

Imagine a variable is a box where you store a piece of data (for example, the number 42). That box is stored on a giant shelf (RAM) and has a label with its location (for example, Shelf 0x001).

  • A Variable is the box with the value (42).
  • A Pointer is a piece of paper where you have written down the address of the shelf (0x001).

If you have the paper (the pointer), you can go to the shelf and see what’s inside, or even change it.

In technical terms, a pointer is a value that contains the memory address of another value.

The & and * operators

To work with pointers we need to distinguish two operators.

The & operator

It reads as “address of”. Placed before a variable, it tells us where it is stored in memory.

numero := 10
direccion := &numero // "Give me the memory address of 'numero'"

fmt.Println(numero)    // Prints: 10
fmt.Println(direccion) // Prints: 0xc0000140b0 (A hexadecimal address)
Copied!

The type of the direccion variable is *int (pointer to integer).

The * operator

It reads as “value at” (or dereference). Placed before a pointer, it travels to that memory address and gives us access to the content.

// Continuing from the previous example...
valor := *direccion // "Go to 0xc0000140b0 and tell me what's there"

fmt.Println(valor) // Prints: 10

// We can modify it!
*direccion = 20 // "Go to that address and put a 20"
fmt.Println(numero) // Prints: 20 (The original variable has changed!)
Copied!

Mnemonic rule:

  • & (Address): WHERE are you?
  • * (Value): WHAT do you have inside?

When to use pointers

The two common reasons are sharing a mutation and avoiding certain copies.

Modifying variables inside functions

As we saw in the previous article, Go passes arguments by copy. If you want a function to change an original variable, you must pass it a pointer.

// Receives a pointer to int (*int)
func Duplicar(n *int) {
    *n = *n * 2 // We modify the value AT the address
}

func main() {
    x := 5
    Duplicar(&x) // We pass the ADDRESS of x
    fmt.Println(x) // Prints: 10
}
Copied!

Avoiding large copies

If we pass a large structure by value, semantics require copying it. By passing a pointer we copy only the address and can avoid that data copy.

This doesn’t mean a pointer is always faster. It can cause the data to escape to the heap, add indirections, and increase the garbage collector’s workload. For small structures, a value is usually simpler and can perform better; in critical code it’s worth measuring.

Pointer arithmetic

At this point Go quite earns the “safe” label.

In C, if you have a pointer pointing to a memory location, you can do pointer + 1 to access the next memory location. This is very powerful, but if you make a mistake, you can read memory from another program or the operating system, causing blue screens or security vulnerabilities (Buffer Overflow).

In standard Go, pointer arithmetic is not allowed.

ptr := &x
// ptr++ // COMPILATION ERROR: non-numeric type *int
Copied!

The unsafe package allows low-level operations that bypass these guarantees, but it’s reserved for very specific cases. We don’t need it for normal pointer usage.

The nil value

The Zero Value of a pointer is nil. This means it is not pointing to any valid address.

The programmer’s panic: If you try to dereference (*) a pointer that is nil, your program will explode (panic).

var p *int // p is nil
// fmt.Println(*p) // PANIC: invalid memory address or nil pointer dereference
Copied!

Always make sure a pointer is not nil before using it, or that it has been initialized.