27. Pointers

Chapter 26 ended with a problem. A method with a value receiver gets a copy, so it cannot change the original, and assigning back after every call is easy to forget. This chapter introduces the pointer, which lets a function or a method reach the original value.

What a pointer is

Every variable lives somewhere in the computer's memory. A pointer is a value that holds that location. If a variable is a house, a pointer is its address written on a piece of paper: copying the paper does not copy the house.

Two symbols do the work:

  • &score means "the location of score". It makes a pointer.
  • *p means "the value at the location p holds". It follows a pointer.
score := 10

p := &score
fmt.Println(*p) // 10

*p = 20
fmt.Println(score) // 20

p is not a second score. It leads to the one score, so assigning through *p changes score. The type of p is written *int, read "a pointer to an int". If you print p itself, you get the location as a number, such as 0xc000012120, which changes from run to run. That number is rarely useful.

Go has no arithmetic on pointers. You can follow one and copy one, but you cannot add to one to reach a neighbouring location.

Passing a pointer to a function

Chapter 21 said that changing a parameter does not change the caller's variable. Pass a pointer instead, and the function can follow it back to the original:

func addBonus(n *int, bonus int) {
    *n = *n + bonus
}

func main() {
    score := 20
    addBonus(&score, 5)
    fmt.Println(score) // 25
}

The parameter's type is *int, and the call passes &score. The call shows that score may change, which is a good thing for a reader to see. These examples are in exercises/27/01-pointer-basics.go.

Passing the wrong one of the two does not compile:

func addBonus(n *int, bonus int) {
    *n = *n + bonus
}

func main() {
    score := 20
    addBonus(score, 5)
    fmt.Println(score)
}
cannot use score (variable of type int) as *int value in argument to addBonus

Pointer receivers

A method's receiver is a parameter, so it can be a pointer too. A pointer receiver has * in front of the type. This is the counter from Chapter 26, without the assigning back:

// Chapter 27 example: a method with a pointer receiver changes the original.
// Run with: go run exercises/27/02-counter.go

package main

import "fmt"

type counter struct {
    count int
}

func (c *counter) step() {
    c.count = c.count + 1
}

func main() {
    c := counter{}
    c.step()
    c.step()
    c.step()
    fmt.Println(c.count) // 3
}

Read (c *counter) as step receives a pointer to a counter. Two things to notice:

  • Inside the method, you write c.count, not (*c).count. When the value in front of a dot is a pointer to a struct, Go follows the pointer for you.
  • The call is c.step(), although c is a counter and step wants a pointer. When a variable holds the value, Go takes its location for you, as if you had written (&c).step(). You will see this all the time.

Go can only do that for something that has a location, such as a variable. A value that was never stored anywhere has none, so this does not compile:

type counter struct {
    count int
}

func (c *counter) step() {
    c.count = c.count + 1
}

func main() {
    counter{}.step()
}
cannot call pointer method step on counter

A point that moves

A point has an x and a y, and a method that measures the distance to another point:

// Chapter 27 example: a point with a pointer receiver.
// Run with: go run exercises/27/03-point.go

package main

import (
    "fmt"
    "math"
)

type point struct {
    x float64
    y float64
}

func (p *point) distance(other point) float64 {
    dx := p.x - other.x
    dy := p.y - other.y
    return math.Sqrt(dx*dx + dy*dy)
}

func main() {
    a := point{x: 0, y: 0}
    b := point{x: 3, y: 4}
    fmt.Println(a.distance(b)) // 5

    // Try this: add a method move(dx float64, dy float64) with a pointer
    // receiver, call it on a, and print a.distance(b) again.
}

a.distance(b) calls the method with a as the receiver and b as the argument. It prints 5, because the result is a float64 with nothing after the point.

distance changes nothing, so a value receiver would work. But a point that moves needs a pointer receiver, and Go programmers do not mix the two on one type. The last section of this chapter comes back to this.

Open exercises/27/03-point.go. Add a method move(dx float64, dy float64) with a pointer receiver, which adds dx to p.x and dy to p.y. Call it on a, then print a.distance(b) again.

A second type: character

A small character that can take damage. The file is exercises/27/04-character.go:

type character struct {
    name  string
    hp    int
    maxHP int
}

func newCharacter(name string, hp int) *character {
    return &character{name: name, hp: hp, maxHP: hp}
}

func (c *character) takeDamage(amount int) {
    c.hp = c.hp - amount
    if c.hp < 0 {
        c.hp = 0
    }
}

func (c *character) heal(amount int) {
    c.hp = c.hp + amount
    if c.hp > c.maxHP {
        c.hp = c.maxHP
    }
}

func (c *character) isAlive() bool {
    return c.hp > 0
}

func main() {
    keiko := newCharacter("Keiko", 100)

    keiko.takeDamage(30)
    fmt.Println(keiko.hp) // 70

    keiko.heal(50)
    fmt.Println(keiko.hp) // 100

    fmt.Println(keiko.isAlive()) // true
}

&character{...} makes a new character and gives you a pointer to it, so newCharacter returns a *character. Returning a pointer to something made inside a function is safe in Go. The character stays alive for as long as any pointer leads to it. Each call to newCharacter makes a separate character with its own fields.

Copy or share

Assigning a struct copies it. Assigning a pointer copies only the pointer, so both variables lead to the same struct:

a := character{name: "Ada", hp: 100}
b := a
b.hp = 1
fmt.Println(a.hp) // 100

c := &character{name: "Ben", hp: 100}
d := c
d.hp = 1
fmt.Println(c.hp) // 1

You have seen this kind of sharing before. Chapter 22 showed that assigning a slice shares its items, and that a function can change a slice's items with no pointer in sight. That is because a slice holds a pointer to its items inside it. Only the items are shared, which is why a function that grows a slice must still return it. Maps behave the same way, as Chapter 23 showed. So you rarely need a pointer to a slice or to a map.

A struct copy copies each field, and a slice field still leads to the same items:

type team struct {
    names []string
}

func main() {
    a := team{names: []string{"Ada"}}
    b := a
    b.names[0] = "Ben"
    fmt.Println(a.names[0]) // Ben
}

nil: a pointer to nothing

The zero value of a pointer is nil, the same nil you check for with err != nil. A nil pointer leads nowhere, so following it crashes the program while it runs. This is exercises/27/05-nil-panic.go, inside main:

var keiko *character
fmt.Println(keiko == nil) // true
fmt.Println(keiko.hp)

It prints true, and then:

panic: runtime error: invalid memory address or nil pointer dereference

A few more lines follow this one. Among them, Go gives the file and line of the crash. This is the crash you will meet most often in Go. It means that a pointer was nil on the line that it names. To dereference a pointer is to follow it. The same crash happens inside a method, on the first line that reads a field through a nil receiver.

nil is also useful, because it can mean "no answer". A function that returns a pointer can return nil when it has nothing to give, and the caller checks first. This is exercises/27/06-nil-check.go:

// Chapter 27 example: a function that returns nil for "no answer", and a
// caller that checks for it.
// Run with: go run exercises/27/06-nil-check.go

package main

import "fmt"

type character struct {
    name string
    hp   int
}

// stronger returns whichever character has more hp, or nil for a tie.
func stronger(a *character, b *character) *character {
    if a.hp > b.hp {
        return a
    }
    if b.hp > a.hp {
        return b
    }
    return nil
}

func announce(winner *character) {
    if winner == nil {
        fmt.Println("It is a tie.")
        return
    }
    fmt.Println(winner.name, "is stronger.")
}

func main() {
    ada := &character{name: "Ada", hp: 100}
    ben := &character{name: "Ben", hp: 80}
    cara := &character{name: "Cara", hp: 100}

    announce(stronger(ada, ben))
    announce(stronger(ada, cara))
}

Output:

Ada is stronger.
It is a tie.

A function returning a pointer that may be nil should say so in its comment, as stronger does. Whoever calls it must check.

A pointer you have already used

bufio.NewReader returns a *bufio.Reader, a pointer. A reader has to remember how far it has read, so its methods have pointer receivers. When a function needs the reader as a parameter, that is the type to write:

func readLine(reader *bufio.Reader, prompt string) (string, error) {
    fmt.Print(prompt)
    text, err := reader.ReadString('\n')
    line := strings.TrimSpace(text)
    if err != nil && line == "" {
        return "", err
    }
    return line, nil
}

func main() {
    reader := bufio.NewReader(os.Stdin)

    name, err := readLine(reader, "Name: ")
    if err != nil {
        fmt.Println("No more input.")
        return
    }
    fmt.Println("Hello,", name)
}

Copies of the pointer all lead to the one reader, so nothing it has already read is lost. The version in Chapter 21 kept the reader in a variable outside the functions. Both work, and this one shows from the call what the function uses.

When to use a pointer receiver

For the types you write in this book, three rules are enough:

  1. If a method must change the value, it needs a pointer receiver.
  2. If the type has one such method, give all its methods pointer receivers, so that nobody has to remember which is which.
  3. If no method changes the value, and it has only a few small fields, like a rectangle, use value receivers.

When in doubt, use a pointer receiver. Most structs that have state end up with them.

Homework

Homework starters are in exercises/27/homework/. Each one runs as it is and prints a placeholder line. Define your types and methods outside main.

Problem 1 — Point with move

Open exercises/27/homework/01-point.go. Build the point struct as above, plus a move(dx, dy) method that adds to the position. Make two points, print the distance between them, move one, print its position, and print the distance again.

Problem 2 — Character

Open exercises/27/homework/02-character.go. Build the character struct with newCharacter(name, hp), takeDamage(amount), heal(amount) and isAlive(). Add a report() method that prints a line like Keiko: 70 / 100 HP (alive: true). Walk through a small fight: report, damage 30, report, damage 80, report, heal 20, report.

Problem 3 — Rectangle

Open exercises/27/homework/03-rectangle.go. Build a rectangle struct with w and h fields, and methods area() and perimeter(). Decide whether the receivers should be values or pointers. Test it with two rectangles of different sizes.

Challenge — Dog and friend

Open exercises/27/homework/04-dog-friend.go. Build a dog struct with a name field, and a friend field of type *dog. A nil friend means that the dog has no friend. Write newDog(name), which returns a *dog, and a describe() method that prints <name> has no friend. or <name> is friends with <friend's name>.

Make two dogs, and call describe() on both. Make one the friend of the other, and describe both again. Then change the friend's name through the friend field, with rex.friend.name = "Lady", and describe both once more.

Stuck or finished? Open the homework solutions page.