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:
&scoremeans "the location ofscore". It makes a pointer.*pmeans "the value at the locationpholds". It follows a pointer.
score := 10
p := &score
fmt.Println(*p) // 10
*p = 20
fmt.Println(score) // 20p 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(), althoughcis a counter andstepwants 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) // 1You 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:
- If a method must change the value, it needs a pointer receiver.
- If the type has one such method, give all its methods pointer receivers, so that nobody has to remember which is which.
- 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.