Kodewick
Start learning →
Lesson 11 of 24Contents

Pointers

Lesson 11 of 24 · Part 3 · Types · about 15 minutes

Every variable lives somewhere in memory, and that place is its address. A pointer is a value that holds an address: it points to a variable. Through a pointer you can read the variable it points to, and change it.

prog.go
1package main2 3import "fmt"4 5func main() {6	price := 1007	p := &price8	fmt.Println(*p)9	*p = 8010	fmt.Println(price)11}
Output
100
80
CodeMeaning
&priceThe address of price.
p := &pricep points to price.
*pThe variable p points to: you can read it and assign to it.

*p = 80 changes price itself. Press Step and see p drawn as an arrow pointing at price. The address itself is a long number that can change from run to run, so this course never shows it.

Pointer types

A pointer to an int has the type *int; a pointer to a string is a *string. The zero value of a pointer is nil: it points to nothing.

prog.go
1package main2 3import "fmt"4 5func main() {6	count := 37	var p *int8	fmt.Println(p == nil)9	p = &count10	fmt.Printf("%T %d\n", p, *p)11	q := new(int)12	*q = 713	fmt.Println(*q)14}
Output
true
*int 3
7

new(int) makes a new int set to zero and gives back a pointer to it. You won't need it often: & is usually enough.

Functions get a copy

As lesson 5 showed, a function's parameters are copies. Change one inside the function and the caller's variable stays the same:

prog.go
1package main2 3import "fmt"4 5func addBonus(salary int) {6	salary += 5007	fmt.Println("Inside:", salary)8}9 10func main() {11	pay := 300012	addBonus(pay)13	fmt.Println("Outside:", pay)14}
Output
Inside: 3500
Outside: 3000

Passing a pointer

To let a function change the caller's variable, pass in the variable's address. When the function changes it through the pointer, it changes the one outside:

prog.go
1package main2 3import "fmt"4 5func addBonus(salary *int) {6	*salary += 5007}8 9func main() {10	pay := 300011	addBonus(&pay)12	fmt.Println(pay)13}
Output
3500

Press Step and go into addBonus: salary is an arrow pointing at pay in main. Compare it with the program before, where salary is a new box holding 3000.

Guess first

What does this show?

1package main2 3import "fmt"4 5func double(n *int) {6	*n = *n * 27}8 9func main() {10	a := 511	b := a12	double(&b)13	fmt.Println(a, b)14}

Pointers to structs

Pointers most often point to structs. To get a field through a struct pointer, just write a.Balance; Go turns it into (*a).Balance for you:

prog.go
1package main2 3import "fmt"4 5type Account struct {6	Owner   string7	Balance int8}9 10func deposit(a *Account, amount int) {11	a.Balance += amount12}13 14func main() {15	acc := Account{Owner: "Maya", Balance: 100}16	deposit(&acc, 50)17	fmt.Println(acc.Balance)18	p := &Account{Owner: "Leo"}19	deposit(p, 30)20	fmt.Println(p.Owner, p.Balance)21	fmt.Println(p)22}
Output
150
Leo 30
&{Leo 30}

&Account{…} makes a new struct and gives you a pointer to it. When fmt shows a struct pointer, it puts an & in front.

nil pointers

A nil pointer points to nothing. Read or write through it and the program panics:

prog.go
1package main2 3import "fmt"4 5type Account struct {6	Owner   string7	Balance int8}9 10func main() {11	var acc *Account12	fmt.Println(acc == nil)13	acc.Balance = 10014	fmt.Println("Never gets here")15}
Output
true
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x?]

goroutine 1 [running]:
main.main(...)
	/tmp/sandbox/prog.go:13 +0x1d
exit status 2

What happened: This pointer is nil: there is nothing to read or write through it.

invalid memory address or nil pointer dereference means: the program tried to reach a variable through a nil pointer, which doesn't point to anything. Press "See how it went wrong" and it stops on line 13. When a pointer might be nil, check first with if acc != nil.

When to use a pointer

  • When a function needs to change the caller's variable or struct.
  • When a struct is big and you don't want to copy it on every call.
  • When you want nil to mean "nothing yet".

Slices and maps don't need a pointer for a function to change their elements: they already share their underlying data with the caller (lessons 7 and 8).

prog.go
1package main2 3import "fmt"4 5func discount(prices []int) {6	for i := range prices {7		prices[i] = prices[i] * 9 / 108	}9}10 11func main() {12	prices := []int{100, 50}13	discount(prices)14	fmt.Println(prices)15}
Output
[90 45]

But if the function makes the slice longer with append, the caller won't see that: in that case, have the function return the new slice.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Swap two numbers with pointers

Finish swap so it swaps the values of the two variables the pointers point to. The program swaps two pairs.

It should print
2 1
70 30
exercise1.go
1package main2 3import "fmt"4 5func swap(a, b *int) {6}7 8func main() {9	x, y := 1, 210	swap(&x, &y)11	fmt.Println(x, y)12	m, n := 30, 7013	swap(&m, &n)14	fmt.Println(m, n)15}
Output
Press Run or Ctrl+Enter
Exercise 2

Put a product on sale

Finish applyDiscount so it lowers the product's price by percent% through the pointer (percent 10 means 10% off). The program puts two products on sale.

It should print
Desk lamp 180
Mug 60
exercise2.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Price int8}9 10func applyDiscount(p *Product, percent int) {11}12 13func main() {14	lamp := Product{Name: "Desk lamp", Price: 200}15	mug := Product{Name: "Mug", Price: 75}16	applyDiscount(&lamp, 10)17	applyDiscount(&mug, 20)18	fmt.Println(lamp.Name, lamp.Price)19	fmt.Println(mug.Name, mug.Price)20}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it: a pointer with no account

This program panics. Make acc point to an account that belongs to Maya with a balance of 0, and the program shows the balance after two deposits.

It should print
Maya 100
Maya 150
exercise3.go
1package main2 3import "fmt"4 5type Account struct {6	Owner   string7	Balance int8}9 10func deposit(a *Account, amount int) {11	a.Balance += amount12}13 14func main() {15	var acc *Account16	deposit(acc, 100)17	fmt.Println(acc.Owner, acc.Balance)18	deposit(acc, 50)19	fmt.Println(acc.Owner, acc.Balance)20}
Output
Press Run or Ctrl+Enter

What you learned

  • &x is the address of x; p := &x makes p point to x, and p has the type *int.
  • *p is the variable p points to, for reading and changing; the zero value of a pointer is nil.
  • Parameters are copies; to change the caller's variable, pass its address f(&x) and change it with *p.
  • With a struct pointer, write p.Balance for a field; &Account{…} makes a struct and gives you its pointer.
  • Reading or writing through a nil pointer panics: nil pointer dereference.
  • Slices and maps let a function change their elements without a pointer.
Finished this lesson?Mark it. Progress is saved in this browser, and you can undo it.