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:
- If the method changes the receiver, use a pointer receiver.
- 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. - If any method on the type needs a pointer receiver, give all its methods pointer receivers, so the type behaves consistently.
- Otherwise (small types that are only read, like
Rector 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:
| Type | Its method set contains |
|---|---|
Counter | methods with a Counter receiver |
*Counter | methods 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
float64or slices. - The method set of
Thas only value-receiver methods;*Thas all of them. This decides which types satisfy an interface. - A
String() stringmethod controls howfmtprints your type.