Declaring Methods
A method is a function with a receiver, and you can attach one to any type you define, not only to structs.
func (receiverName ReceiverType) MethodName(params) results { ... }A method is a function that belongs to a type. It is written like a normal function with one extra part in front of the name: the receiver. In func (c Celsius) Fahrenheit() float64, the receiver is (c Celsius). Celsius is the type the method belongs to, and c is the name the body uses for the value it was called on. You call it with a dot: t.Fahrenheit().
Not only structs
Any type you define can have methods. type Celsius float64 is a new type with float64 underneath, and it can have as many methods as you like. So can type Names []string and type Counts map[string]int. This is how Go turns a plain number or list into something with behaviour, without needing a struct to wrap it.
Two rules
The receiver type must be a type defined in the same package as the method. You cannot add a method to int, or to a type that came from another package. Define your own type first, then the methods are yours. The receiver type also cannot itself be a pointer type: func (p *Point) ... is allowed, but type P *Point cannot carry methods.
The longhand
A method call is shorthand. t.Fahrenheit() can also be written Celsius.Fahrenheit(t), which is called a method expression: the receiver becomes the first argument of an ordinary function. They are the same call. Writing f := t.Fahrenheit without the brackets stores the method together with its receiver, to call later.
Example
package main
import (
"fmt"
"strings"
)
// Celsius is a type of its own, with float64 as its underlying type.
type Celsius float64
// Fahrenheit is a method on Celsius. (c Celsius) is the receiver.
func (c Celsius) Fahrenheit() float64 {
return float64(c)*9/5 + 32
}
// Names is a named slice type. Methods work on it just as well.
type Names []string
// List joins the names with commas.
func (n Names) List() string {
return strings.Join(n, ", ") // Names may be passed where []string is wanted
}
func main() {
var t Celsius = -40
fmt.Println(t.Fahrenheit())
n := Names{"ada", "grace"}
fmt.Println(n.List())
}
// Output:
// -40
// ada, grace
When to use it
- Give a Celsius or Money type its own conversion and formatting methods.
- Add a Total method to a named slice type instead of keeping a loose helper function.
- Keep a Rectangle's behaviour next to its fields so callers find it from the dot.
- Wrap a type from another package in your own named type so you can give it methods.
More examples
The shorthand and the longhand of a call
All three lines are the same call written three ways. The method expression Counter.Plus has the type func(Counter, Counter) Counter, which is useful when something wants a plain function. The method value c.Plus is how you hand a configured piece of behaviour to another function to run later.
package main
import "fmt"
type Counter int
func (c Counter) Plus(n Counter) Counter {
return c + n
}
func main() {
c := Counter(5)
// Shorthand: the ordinary method call.
fmt.Println(c.Plus(3))
// Longhand: a method expression. The receiver is the first argument.
fmt.Println(Counter.Plus(c, 3))
// A method value: the receiver is fixed now, the call happens later.
add := c.Plus
fmt.Println(add(3))
}
// Output:
// 8
// 8
// 8
Methods on a named slice type
Reach for this when a group of helper functions all take the same slice as their first argument. Naming the type collects them in one place, and callers discover them by typing s. in an editor instead of hunting for function names.
package main
import "fmt"
// You cannot declare a method on []int, because that type is not yours.
// Define your own named type, and then it is.
type Scores []int
func (s Scores) Total() int {
total := 0
for _, v := range s {
total += v
}
return total
}
func (s Scores) Highest() int {
best := 0
for _, v := range s {
if v > best {
best = v
}
}
return best
}
func main() {
s := Scores{3, 9, 4}
fmt.Println(s.Total(), s.Highest())
}
// Output:
// 16 9
A method calling another method on the same receiver
Inside a method the receiver is just a normal variable, so r.Area() works exactly as it would from outside. Build the small methods first and let the larger ones call them, so a change to the formula happens in one place.
package main
import "fmt"
type Rect struct {
W, H float64
}
func (r Rect) Area() float64 {
return r.W * r.H
}
// Describe uses Area rather than repeating the multiplication.
func (r Rect) Describe() string {
return fmt.Sprintf("%gx%g covers %g", r.W, r.H, r.Area())
}
func main() {
r := Rect{W: 3, H: 2}
fmt.Println(r.Describe())
}
// Output:
// 3x2 covers 6
Discussion