Methods

A method is a function that belongs to a type. Instead of area(r) you write r.Area(), and the behavior lives right next to the data it works on. In this lesson you’ll add methods to your own types, learn when a method needs a pointer receiver (with a simple rule of thumb), and give a type its own String() so it prints nicely.

Your first method

A method looks like a function with one extra parameter in front of its name, the receiver:

package main

import "fmt"

type Rect struct {
	Width, Height float64
}

func (r Rect) Area() float64 {
	return r.Width * r.Height
}

func (r Rect) Perimeter() float64 {
	return 2 * (r.Width + r.Height)
}

func main() {
	r := Rect{Width: 3, Height: 4}
	fmt.Println(r.Area())
	fmt.Println(r.Perimeter())
}Code language: Go (go)
12
14Code language: plaintext (plaintext)

func (r Rect) Area() float64 reads as “a method Area on the type Rect, returning a float64“. Inside, r is the value the method was called on. You call it with a dot, just like reading a field: r.Area().

There’s nothing a method can do that a plain function func Area(r Rect) float64 couldn’t. The benefits are organization (everything you can do with a Rect shows up in one place, and in your editor’s autocomplete), shorter names (Area instead of RectArea, since Circle can have its own Area), and, most importantly, methods are what let a type satisfy an interface, which is the next lesson.

The receiver name is usually one or two letters taken from the type name (r for Rect, c for Counter). Go doesn’t use this or self.

Value receivers and pointer receivers

The receiver works exactly like any other parameter, and you know from Structs that structs are copied when passed to a function. So a method with a value receiver gets a copy, and any change it makes is lost. To change the original, use a pointer receiver, *Counter:

package main

import "fmt"

type Counter struct {
	n int
}

func (c Counter) IncBroken() {
	c.n++ // changes a copy
}

func (c *Counter) Inc() {
	c.n++ // changes the original
}

func (c Counter) Value() int {
	return c.n
}

func main() {
	var c Counter

	c.IncBroken()
	fmt.Println(c.Value())

	c.Inc()
	c.Inc()
	fmt.Println(c.Value())
}Code language: Go (go)
0
2Code language: plaintext (plaintext)

Notice that I called c.Inc() on a plain Counter variable, not a pointer. Go sees that Inc wants a *Counter and takes the address for you, so c.Inc() means (&c).Inc(). It works the other way too: if p is a *Counter, p.Value() follows the pointer automatically. You almost never need to write & or * to call a method.

The automatic & only works on something that has an address, like a variable or a slice element. A temporary value doesn’t, so this fails:

package main

type Counter struct {
	n int
}

func (c *Counter) Inc() {
	c.n++
}

func main() {
	Counter{}.Inc()
}Code language: Go (go)
./main.go:12:12: cannot call pointer method Inc on CounterCode language: plaintext (plaintext)

The same thing happens with a struct stored in a map (m["a"].Inc()), for the reason you saw in Maps: a map lookup gives you a copy.

Which receiver should I use?

Here’s the rule of thumb I follow:

  1. If the method changes the receiver, use a pointer receiver.
  2. If the struct is large, or holds something that must not be copied (like a sync.Mutex, which you’ll meet in WaitGroups and mutexes), use a pointer receiver.
  3. If any method on the type needs a pointer receiver, give all its methods pointer receivers, so the type behaves consistently.
  4. Otherwise (small types that are only read, like Rect or a temperature), a value receiver is fine.

When in doubt, use a pointer receiver. I broke rule 3 in the Counter example to show the difference; in real code, Value would have a *Counter receiver too.

Methods on non-struct types

Methods aren’t only for structs. You can define a new type based on any other type and give it methods:

package main

import "fmt"

type Celsius float64

func (c Celsius) Fahrenheit() float64 {
	return float64(c)*9/5 + 32
}

type Words []string

func (w Words) Longest() string {
	longest := ""
	for _, word := range w {
		if len(word) > len(longest) {
			longest = word
		}
	}
	return longest
}

func main() {
	temp := Celsius(21.5)
	fmt.Println(temp.Fahrenheit())

	w := Words{"go", "gopher", "slice"}
	fmt.Println(w.Longest(), len(w))
}Code language: Go (go)
70.7
gopher 3Code language: plaintext (plaintext)

Celsius is a new type whose underlying type is float64. It supports everything a float64 does, and to use it as a plain float64 you convert it, like float64(c). Words is still a slice, so len and range work on it. A type like Celsius also stops you from passing a Fahrenheit value by accident, because the types don’t match.

The one restriction: you can only add methods to types defined in your own package. You can’t add a method to int or string directly:

package main

import "fmt"

func (i int) Double() int {
	return i * 2
}

func main() {
	fmt.Println(3)
}Code language: Go (go)
./main.go:5:9: cannot define new methods on non-local type intCode language: plaintext (plaintext)

The fix is what Celsius does: type MyInt int, then add the method to MyInt.

Embedded methods are promoted

In the structs lesson you saw that embedding a struct promotes its fields. Its methods are promoted the same way:

package main

import "fmt"

type Address struct {
	City, Country string
}

func (a Address) Label() string {
	return a.City + ", " + a.Country
}

type Customer struct {
	Name string
	Address
}

func main() {
	c := Customer{Name: "Ana", Address: Address{City: "Lisbon", Country: "Portugal"}}
	fmt.Println(c.Name, "lives in", c.Label())
}Code language: Go (go)
Ana lives in Lisbon, PortugalCode language: plaintext (plaintext)

c.Label() is shorthand for c.Address.Label(), and the method still only sees the Address, not the whole customer.

Method sets: which methods a type has

Calling methods is forgiving, thanks to the automatic & and *. But Go also keeps a precise list of the methods each type has, called its method set, and that list matters for interfaces:

TypeIts method set contains
Countermethods with a Counter receiver
*Countermethods with a Counter receiver and methods with a *Counter receiver

In other words, a pointer has every method, while a plain value only has the value-receiver ones. An interface is a list of methods (that’s the next lesson), and a type can only be used as that interface if its method set contains them all. Here’s the error you’ll get when a method has a pointer receiver but you try to use a plain value:

package main

import "fmt"

type Incrementer interface {
	Inc()
}

type Counter struct {
	n int
}

func (c *Counter) Inc() {
	c.n++
}

func main() {
	var i Incrementer = Counter{}
	fmt.Println(i)
}Code language: Go (go)
./main.go:18:22: cannot use Counter{} (value of struct type Counter) as Incrementer value in variable declaration: Counter does not implement Incrementer (method Inc has pointer receiver)Code language: plaintext (plaintext)

The message tells you exactly what’s wrong: Inc has a pointer receiver, so only *Counter has it. Writing &Counter{} fixes it. Keep this error in mind; you’ll see it again.

A String() method

If your type has a method String() string, the fmt functions use it whenever they print a value of that type:

package main

import "fmt"

type Celsius float64

func (c Celsius) String() string {
	return fmt.Sprintf("%.1f°C", float64(c))
}

type Rect struct {
	Width, Height float64
}

func (r Rect) String() string {
	return fmt.Sprintf("%g×%g rectangle", r.Width, r.Height)
}

func main() {
	fmt.Println(Celsius(21.5))
	fmt.Println(Rect{Width: 3, Height: 4})
	fmt.Printf("It's %v today\n", Celsius(-3))
}Code language: Go (go)
21.5°C
3×4 rectangle
It's -3.0°C todayCode language: plaintext (plaintext)

Inside Celsius.String I convert to float64 before formatting. That matters: formatting c itself with %v would call String again, which would call it again, forever.

Method sets apply here too. If you give String a pointer receiver, fmt.Println(t) on a plain value won’t find it and prints the raw value; only fmt.Println(&t) uses your method. For String, a value receiver is usually the right choice.

How does fmt know to look for String? Through an interface called fmt.Stringer. That’s where the next lesson starts.

Practice

1. A bank account. Define an Account struct with a balance field (in cents, as an int). Give it Deposit(amount int), Withdraw(amount int) error (which refuses to go below zero) and Balance() int. Pick the right receivers.

Show a solution
package main

import (
	"errors"
	"fmt"
)

type Account struct {
	balance int
}

func (a *Account) Deposit(amount int) {
	a.balance += amount
}

func (a *Account) Withdraw(amount int) error {
	if amount > a.balance {
		return errors.New("insufficient funds")
	}
	a.balance -= amount
	return nil
}

func (a *Account) Balance() int {
	return a.balance
}

func main() {
	var acc Account
	acc.Deposit(5000)

	if err := acc.Withdraw(1200); err != nil {
		fmt.Println("error:", err)
	}
	if err := acc.Withdraw(10000); err != nil {
		fmt.Println("error:", err)
	}
	fmt.Println("balance:", acc.Balance())
}Code language: Go (go)
error: insufficient funds
balance: 3800Code language: plaintext (plaintext)

Deposit and Withdraw change the account, so they need pointer receivers, and following rule 3, Balance gets one too.

2. A stack. Define type Stack []int with methods Push(v int), Pop() (int, bool) (returning false when the stack is empty) and Len() int. Push 1, 2 and 3, then pop until the stack is empty.

Show a solution
package main

import "fmt"

type Stack []int

func (s *Stack) Push(v int) {
	*s = append(*s, v)
}

func (s *Stack) Pop() (int, bool) {
	if len(*s) == 0 {
		return 0, false
	}
	last := len(*s) - 1
	v := (*s)[last]
	*s = (*s)[:last]
	return v, true
}

func (s *Stack) Len() int {
	return len(*s)
}

func main() {
	var s Stack
	s.Push(1)
	s.Push(2)
	s.Push(3)
	fmt.Println("len:", s.Len())

	for {
		v, ok := s.Pop()
		if !ok {
			break
		}
		fmt.Println(v)
	}
	fmt.Println("len:", s.Len())
}Code language: Go (go)
len: 3
3
2
1
len: 0Code language: plaintext (plaintext)

Push and Pop change the slice header (its length, and on growth its backing array), so they need a pointer receiver; *s = append(*s, v) stores the new slice back. With a value receiver, the appends would be lost, just like forgetting s = append(s, v).

Key takeaways

  • A method is a function with a receiver: func (r Rect) Area() float64.
  • A value receiver works on a copy. Use a pointer receiver when the method changes the receiver or the type is large, and then use pointer receivers for all of the type’s methods.
  • Go adds & and * for you when calling methods on variables.
  • Any type you define in your package can have methods, including ones based on float64 or slices.
  • The method set of T has only value-receiver methods; *T has all of them. This decides which types satisfy an interface.
  • A String() string method controls how fmt prints your type.