Methods
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():
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}
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:
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}
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:
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}
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:
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}
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.
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}
Deposit has a value receiver, so it adds to a copy and acc doesn't change. Withdraw has a pointer receiver, so it really takes off 30. That makes 100 - 30 = 70.
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.
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}
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:
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}
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:
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}
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.
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.
14 22
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}
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.
2000 5500
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}
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.
$12.50 $3.99
1package main2 3import "fmt"4 5type Price int6 7func main() {8 fmt.Println(Price(1250))9 fmt.Println(Price(399))10}
What you learned
- A method is a function with a receiver:
func (r Rect) Area() float64, called asbox.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
typecan have methods, liketype Celsius float64. - When a type has a
String() stringmethod,fmtuses it to show the value.