What is a pointer

Every variable in a running program lives somewhere in memory, at a specific address. A pointer is a value that holds one of those addresses. Instead of carrying the data itself, you carry the location; anyone with the pointer can look up the data, and anyone who writes through the pointer changes the original storage.

You have already met this idea at the collection level. Two slice variables can share an underlying array; two map variables can share a hash table. Pointers let you do the same thing with a single value.

The & and * operators

&x produces a pointer to the variable x:

x := 42
p := &x
fmt.Println(p)   // some hex address, e.g. 0xc00001a0b0

p is of type *int, read as "pointer to int". Its value is the address of x.

*p goes the other way: given a pointer, it reads (or writes) the value at that address. This is called dereferencing the pointer:

x := 42
p := &x

fmt.Println(*p)   // 42
*p = 100
fmt.Println(x)    // 100

*p = 100 wrote through the pointer into the storage backing x. The next line prints x directly and sees the update. x and *p are two different names for the same location.

Pointer types look like *T

The type of a pointer is the target type with a * in front:

  • *int is a pointer to an int
  • *string is a pointer to a string
  • *Point (once you meet structs in the next chapter) is a pointer to a Point

int and *int are different types. The compiler keeps them separate: you cannot pass a *int to a function expecting int, or vice versa.

Copying a pointer copies the address, not the value

This is the whole point of pointers. When you copy a pointer, both pointers aim at the same storage:

x := 42
p := &x
q := p       // q now points at the same place as p

*q = 999
fmt.Println(x)   // 999

p and q are two pointers but they both aim at x. A write through either is visible through the other, and through x itself. Several pieces of code can now refer to and mutate one shared piece of storage.

This is exactly the behaviour you saw with slices in the previous chapter: the slice header was small and copied cheaply, but the underlying array was shared. Pointers apply the same idea to individual values.

Go has no pointer arithmetic

If you are coming from C, notice what is missing. Go does not let you do arithmetic on a pointer:

x := 42
p := &x
p = p + 1   // compile error: invalid operation: p + 1 (mismatched types *int and int)

There is no p + 1 to move to "the next memory cell", no p++, and no way to walk through an array by incrementing a pointer. Pointer arithmetic is the source of most memory-corruption bugs in C, and Go's choice to leave it out is the main reason Go programs very rarely have that whole class of bug.

If you want to walk through a collection, you use slices and range. Pointers are for referring to one value.

Default to values, reach for pointers only when needed

Coming from C, the instinct is to reach for pointers constantly. Resist it. Idiomatic Go uses plain values for most parameters, return types, and struct fields, because passing small values by copy is fast and avoids a whole class of bugs. Only reach for a pointer when you actually need one of the things it buys you: letting a function modify a caller's variable, avoiding the cost of copying a large struct, or being able to represent "not set" with nil. The next three lessons cover each of those cases.

Task

Starter declares n := 10. In main:

  • Create a pointer p to n using &.
  • Print *p on its own line. You should see 10.
  • Assign through the pointer with *p = 25.
  • Print n on its own line. You should see 25, because *p and n refer to the same storage.

Expected output:

10
25
Expected output
10
25