Methods

A method is a function attached to a type. It's declared like a function but with an extra parameter, the receiver, written before the name. Methods give your types behaviour — account.Deposit(50), rect.Area() — and they're how types satisfy interfaces. This lesson covers declaring methods, value versus pointer receivers, methods on non-struct types, and method values.

Declaring a method

GO
package main

import (
	"fmt"
	"math"
)

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)
}

type Circle struct{ Radius float64 }

func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }

func main() {
	r := Rect{Width: 3, Height: 4}
	fmt.Println(r.Area(), r.Perimeter())

	c := Circle{Radius: 1}
	fmt.Printf("%.3f\n", c.Area())
}
Output
12 14
3.142

(r Rect) is the receiver: inside the method, r is the value the method was called on. Different types can have methods with the same name — Rect.Area and Circle.Area don't clash. Receiver names are short by convention, usually one or two letters taken from the type name, and the same name is used in every method of the type (never this or self).

Pointer receivers

A method with a value receiver works on a copy, so it can't change the original. To modify the receiver, use a pointer receiver, (a *Account):

GO
package main

import (
	"errors"
	"fmt"
)

type Account struct {
	Owner   string
	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) Summary() string { // reads only: a value receiver works
	return fmt.Sprintf("%s has %d", a.Owner, a.Balance)
}

func main() {
	acc := Account{Owner: "Ana"}
	acc.Deposit(100) // Go takes &acc for you
	fmt.Println(acc.Withdraw(30), acc.Withdraw(500))
	fmt.Println(acc.Summary())
}
Output
<nil> insufficient funds
Ana has 70

You call pointer methods on an ordinary variable: Go automatically takes its address, so acc.Deposit(100) means (&acc).Deposit(100). The reverse also works — calling a value method on a pointer dereferences it.

Value or pointer receiver?

Here's what goes wrong with a value receiver on a method that's meant to change things:

GO
package main

import "fmt"

type Counter struct{ n int }

func (c Counter) IncBroken() { c.n++ } // increments a copy
func (c *Counter) Inc()      { c.n++ }

func main() {
	var c Counter
	c.IncBroken()
	c.IncBroken()
	fmt.Println(c.n)
	c.Inc()
	c.Inc()
	fmt.Println(c.n)
}
Output
0
2

The guidelines:

  • Use a pointer receiver if the method changes the receiver, if the struct is large, or if it contains something that mustn't be copied, such as a sync.Mutex.
  • Use a value receiver for small, immutable values such as Point, Money or time.Time.
  • Be consistent: if any method of a type needs a pointer receiver, give all its methods pointer receivers.

Methods on any named type

Methods aren't limited to structs. Any type you define in your package can have them — a named number, string, slice or map:

GO
package main

import (
	"fmt"
	"strings"
)

type Celsius float64

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

type Path []string

func (p Path) String() string { return "/" + strings.Join(p, "/") }

func (p Path) Parent() Path {
	if len(p) == 0 {
		return p
	}
	return p[:len(p)-1]
}

func main() {
	t := Celsius(25)
	fmt.Println(t.Fahrenheit())

	p := Path{"home", "ana", "notes"}
	fmt.Println(p.String(), p.Parent().String())
	fmt.Println(p) // fmt uses the String method automatically
}
Output
77
/home/ana/notes /home/ana
/home/ana/notes

You can only declare methods on types defined in the same package, so you can't add a method to int or time.Time directly — define your own type based on them instead. A String() string method is special: fmt uses it to print the value (lesson 42).

Methods on nil receivers

A method with a pointer receiver can be called on a nil pointer. That lets a type treat nil as a valid, empty value:

GO
package main

import "fmt"

type List struct {
	Value int
	Next  *List
}

func (l *List) Sum() int {
	if l == nil {
		return 0
	}
	return l.Value + l.Next.Sum()
}

func main() {
	list := &List{1, &List{2, &List{3, nil}}}
	fmt.Println(list.Sum())

	var empty *List
	fmt.Println(empty.Sum())
}
Output
6
0

Method values and expressions

value.Method without parentheses gives a function bound to that value. Type.Method gives a function that takes the receiver as its first argument:

GO
package main

import "fmt"

type Greeter struct{ Greeting string }

func (g Greeter) Greet(name string) string { return g.Greeting + ", " + name }

func main() {
	hi := Greeter{"Hi"}.Greet // method value: bound to this Greeter
	fmt.Println(hi("Ana"))

	greet := Greeter.Greet // method expression: receiver becomes a parameter
	fmt.Println(greet(Greeter{"Hello"}, "Ben"))
}
Output
Hi, Ana
Hello, Ben

Try it yourself

Give a Stack type Push, Pop and Len methods. Which ones need pointer receivers?

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Output
3
c b a
Next: building types from other types with embedding.