Kodewick
Start learning →
Lesson 12 of 24Contents

Methods

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

A method is a function that belongs to a type. Write (r Rect) between func and the name, and Area becomes a method of Rect, called as box.Area():

prog.go
1package main2 3import "fmt"4 5type Rect struct {6	Width, Height float647}8 9func (r Rect) Area() float64 {10	return r.Width * r.Height11}12 13func main() {14	box := Rect{Width: 3, Height: 4}15	fmt.Println(box.Area())16}
Output
12

(r Rect) is called the receiver. When you call box.Area(), r is box.

Methods and functions

The receiver is really just a parameter written up front. The area function and the Area method below do the same thing; only the way you call them differs:

prog.go
1package main2 3import "fmt"4 5type Rect struct {6	Width, Height float647}8 9func area(r Rect) float64 {10	return r.Width * r.Height11}12 13func (r Rect) Area() float64 {14	return r.Width * r.Height15}16 17func (r Rect) IsSquare() bool {18	return r.Width == r.Height19}20 21func main() {22	box := Rect{Width: 3, Height: 4}23	fmt.Println(area(box), box.Area())24	fmt.Println(box.IsSquare())25}
Output
12 12
false

With methods, everything you can do with a type hangs off that type, so readers know where to look. Different types can have methods with the same name: a Circle can have its own Area.

Value receivers can't change the original

A receiver is a copy, just like any other parameter. So the Inc below adds to a copy, and clicks stays at 0:

prog.go
1package main2 3import "fmt"4 5type Counter struct {6	N int7}8 9func (c Counter) Inc() {10	c.N++11}12 13func main() {14	var clicks Counter15	clicks.Inc()16	clicks.Inc()17	fmt.Println(clicks.N)18}
Output
0

Press Step and go into Inc: c is a new box, c.N becomes 1, and when you're back in main that box is gone.

Pointer receivers

Make the receiver a pointer, (c *Counter), and the method changes the original:

prog.go
1package main2 3import "fmt"4 5type Counter struct {6	N int7}8 9func (c *Counter) Inc() {10	c.N++11}12 13func main() {14	var clicks Counter15	clicks.Inc()16	clicks.Inc()17	fmt.Println(clicks.N)18}
Output
2

clicks isn't a pointer, but clicks.Inc() still works: Go turns it into (&clicks).Inc() for you. Press Step again: this time c is an arrow pointing at clicks in main.

Guess first

What does this show?

1package main2 3import "fmt"4 5type Account struct {6	Balance int7}8 9func (a Account) Deposit(n int) {10	a.Balance += n11}12 13func (a *Account) Withdraw(n int) {14	a.Balance -= n15}16 17func main() {18	acc := Account{Balance: 100}19	acc.Deposit(50)20	acc.Withdraw(30)21	fmt.Println(acc.Balance)22}

Which one to pick

  • The method changes the receiver: use a pointer receiver.
  • The struct is big: use a pointer receiver to skip the copy.
  • By convention, a type's methods all use pointer receivers or all use value receivers, not a mix.

An account: depositing and withdrawing both change the balance, so both use pointer receivers.

prog.go
1package main2 3import "fmt"4 5type Account struct {6	Owner   string7	Balance int8}9 10func (a *Account) Deposit(n int) {11	a.Balance += n12}13 14func (a *Account) Withdraw(n int) bool {15	if n > a.Balance {16		return false17	}18	a.Balance -= n19	return true20}21 22func main() {23	acc := Account{Owner: "Maya", Balance: 100}24	acc.Deposit(20)25	ok := acc.Withdraw(30)26	fmt.Println(ok, acc.Balance)27	ok = acc.Withdraw(500)28	fmt.Println(ok, acc.Balance)29}
Output
true 90
false 90

When there isn't enough money, Withdraw returns false and leaves the balance alone.

Methods on named types

It isn't only structs: any type you name with type can have methods. type Celsius float64 is a new type built on float64; type Scores []int is built on a slice:

prog.go
1package main2 3import "fmt"4 5type Celsius float646 7func (c Celsius) Fahrenheit() float64 {8	return float64(c)*9/5 + 329}10 11type Scores []int12 13func (s Scores) Sum() int {14	total := 015	for _, v := range s {16		total += v17	}18	return total19}20 21func main() {22	t := Celsius(25)23	fmt.Println(t.Fahrenheit())24	s := Scores{90, 75, 88}25	fmt.Println(s.Sum())26}
Output
77
253

float64(c) turns the Celsius back into a plain float64. You can only add methods to types defined in your own package, so not to int or string.

The String method

Give a type a String() string method and fmt uses it whenever it shows a value of that type:

prog.go
1package main2 3import "fmt"4 5type Celsius float646 7func (c Celsius) String() string {8	return fmt.Sprintf("%.1f°C", float64(c))9}10 11func main() {12	t := Celsius(36.6)13	fmt.Println(t)14	fmt.Println([]Celsius{20, 25.5})15	fmt.Printf("Now: %v\n", t)16}
Output
36.6°C
[20.0°C 25.5°C]
Now: 36.6°C

fmt.Sprintf formats just like Printf, but instead of showing the result it returns it as a string (verbs like %.1f are in the table in lesson 10). How does fmt know to call String? That becomes clear in the next lesson, on interfaces.

Practice

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

Exercise 1

Perimeter of a rectangle

Finish the Perimeter method of Rect so it returns the perimeter (width plus height, times 2). The program shows the perimeter of two rectangles.

It should print
14
22
exercise1.go
1package main2 3import "fmt"4 5type Rect struct {6	Width, Height int7}8 9func (r Rect) Perimeter() int {10	return 011}12 13func main() {14	a := Rect{Width: 3, Height: 4}15	b := Rect{Width: 5, Height: 6}16	fmt.Println(a.Perimeter())17	fmt.Println(b.Perimeter())18}
Output
Press Run or Ctrl+Enter
Exercise 2

Fix it: the step counter that doesn't count

Add should add steps to the counter, but the program shows 0 both times. Fix it so it shows 2000 and 5500.

It should print
2000
5500
exercise2.go
1package main2 3import "fmt"4 5type StepCounter struct {6	Steps int7}8 9func (s StepCounter) Add(n int) {10	s.Steps += n11}12 13func main() {14	var today StepCounter15	today.Add(2000)16	fmt.Println(today.Steps)17	today.Add(3500)18	fmt.Println(today.Steps)19}
Output
Press Run or Ctrl+Enter
Exercise 3

Prices that show their currency

Prices are stored as whole cents: Price(1250) is 12.50 dollars. Give Price a String method so fmt.Println shows $ and two decimal places, like $12.50.

It should print
$12.50
$3.99
exercise3.go
1package main2 3import "fmt"4 5type Price int6 7func main() {8	fmt.Println(Price(1250))9	fmt.Println(Price(399))10}
Output
Press Run or Ctrl+Enter

What you learned

  • A method is a function with a receiver: func (r Rect) Area() float64, called as box.Area().
  • A value receiver gets a copy and can't change the original; a pointer receiver (c *Counter) changes the original.
  • Calling a pointer-receiver method on a variable takes its address for you: clicks.Inc().
  • Use a pointer receiver to change the receiver or for big structs; don't mix the two kinds on one type.
  • Any type you define with type can have methods, like type Celsius float64.
  • When a type has a String() string method, fmt uses it to show the value.
Finished this lesson?Mark it. Progress is saved in this browser, and you can undo it.