[Go] Methods & Interfaces

Methods

You can define methods on types. A method is a function with a spetial receiver argument. The receiver appears in its own argument list between the func keyword and the method name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import (
"fmt"
"math"
)

type Vertex struct {
X, Y float64
}

func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
v := Vertex{3, 4}
fmt.Println(Abs(v))
}

You can declare a method on non-struct types, too. You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).

Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

func (f Float) Dup() Float {
return f * 2
}

func (f *Float) Dup1() {
*f = *f * 2
}

func main() {
f := MyFloat(-math.sqrt2) // Casting
v := Vertex{3, 4}
v.Scale(10)
f.Dup1()
fmt.Println(v.Abs())
fmt.Println(f.Abs()) // Call method
}

With a value receiver, the Scale method operates on a copy of the original Vertex value. (This is the same behavior as for any other function argument.) The Scale method must have a pointer receiver to change the Vertex value declared in the main function.

Function and Method with Pointer

Functions with a pointer argument must take a pointer:

1
2
3
var v Vertex
ScaleFunc(v, 5) // Compile error!
ScaleFunc(&v, 5) // OK

Methods with pointer receivers take either a value or a pointer as the receiver when they are called:

1
2
3
4
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK

As a convenience, Go interprets the statement v.Scale(5) as (&v).Scale(5) since the Scale method has a pointer receiver.

Functions that take a value argument must take a value of that specific type:

1
2
3
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Compile error!

Methods with value receivers take either a value or a pointer as the receiver when they are called:

1
2
3
4
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK

In this case, the method call p.Abs() is interpreted as (*p).Abs().

Choose a pointer receiver

There are two reasons to use a pointer receiver.

The first is so that the method can modify the value that its receiver points to.

The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, for example.

In this example, both Scale and Abs are with receiver type *Vertex, even though the Abs method needn’t modify its receiver.