Methods in Go (part I)

Michał Łowicki
golangspec
5 min readNov 1, 2016

--

Type defined in Golang program can have methods associated with it. Let’s see an example:

type T struct {
name string
}
func (t T) PrintName() {
fmt.Println(t.name)
}
func main() {
t := T{name: "Michał"}
t.PrintName()
}

As you may suspect the program outputs popular polish name. Method is a function with additional single-element list of parameters called receiver. It’s placed right before the name of a method. What is set as a receiver’s type dictates how method can be used.

The receiver

Methods are bound to receiver’s base type. Small example explains it nicely:

type T struct {
name string
}
func (T) F() {}
func (*T) F() {}

Such code cannot be compiled. First method F is bound to T’s base type which is T. Second method is bound to *T’s base type which is T. For single base type, methods names must be unique so compiler will notice an issue:

> go install github.com/mlowicki/lab && ./spec/bin/lab
# github.com/mlowicki/lab
spec/src/github.com/mlowicki/lab/lab.go:103: method redeclared: T.F
method(T) func()
method(*T) func()

If base type is a struct type then fields identifiers cannot overlap with methods identifiers:

type T struct {
M int
}
func (t T) M() {}

and it causes build to fail with message:

type T has both field and method named M

Go’s type system puts limits on what can be placed as a receiver’s type. It cannot be an interface type or pointer so it’s not possible e.g. to define method for empty interface (interface{}) which is satisfied by all types. It’s allowed to use only type names so type literals cause compile-time error:

func (v map[string]float64) M() {}

with message invalid receiver type map[string]float64 (map[string]float64 is an unnamed type).

Method set

To call method m on a variable of type T, method m must in a method set associated with T. What it means to be a member of method set?

Interface type

Method set of the interface type is the interface itself — list of methods required to be defined in order to implement an interface:

type I interface {
F()
G()
}
type T struct{}func (T) F() {}
func (T) G() {}
func (T) H() {}
func main() {
var i I = T{}
i.F()
i.G()
i.H() // error: i.H undefined (type I has no field or method H)
}

It isn’t allowed to call methods from dynamic type of i variable which is T in the above example. Having interface type value only methods from the interface belong to method set of the interface type (method set is a static thing and doesn’t change when value of different type would be assigned). To call method H, type assertion would need to be used first:

t, ok := i.(T)
if !ok {
log.Fatal("Type assertion failed")
}
t.H()

Non-interface type

For non-interface type T, method set consist of methods with receiver type T:

type T struct{}func (T) F() {}
func (T) G() {}
type U struct{}func (U) H() {}func main() {
t := T{}
t.F()
t.G()
t.H() // error: t.H undefined (type T has no field or method H)
}

While dealing with pointer type *T, its method set consists of methods with receiver type T or *T:

type T struct{}func (T) F() {}
func (T) G() {}
func (*T) H() {}
func main() {
t := &T{}
t.F()
t.G()
t.H()
}

Why method set of T doesn’t have methods with receiver type *T in the same way as method set of *T contains methods with receiver type T? Surprisingly assigning to t not address but non-pointer struct value still works just fine:

t := T{}
t.F()
t.G()
t.H()

Go’s specification describes that case explicitly:

If x is addressable and &x’s method set contains m, x.m() is shorthand for (&x).m().

General tips when to use value or pointer receivers are placed in official FAQ.

Uniqueness

Method set of type T cannot have two methods with the same name. It isn’t possible then to have f.ex. two identically named methods but with different types of parameters (no ad hoc polymorphism in Go).

Method set of type T defines what interfaces are implemented by T.

Printing method set

Go has reflect package which is useful to see the elements of type’s method set:

func PrintMethodSet(val interface{}) {
t := reflect.TypeOf(val)
fmt.Printf("Number of methods: %d\n", t.NumMethod())
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("Method %s\n", m)
fmt.Printf("\tName: %s\n", m.Name)
fmt.Printf("\tPackage path: %s\n", m.PkgPath)
}
}

Type of return value from Method method is Method type.

It’s worth to mention that documentation had a bug since NumMethod returns number of methods from method set but only those which are exported (name starts with a capital letter). Filed as #17686.

Methods for imported type

Methods can be defined only inside the package where type is created.

github.com/mlowicki/lib/lib.go:

package libtype T struct{}

github.com/mlowicki/lab/lab.go:

package mainimport (
"fmt"
. "github.com/mlowicki/lib"
)
func (T) F() {}
func (*T) F() {}
[...]

It causes build error cannot define new methods on non-local type lib.T.

Not used parameters

Function / method definition doesn’t enforce to name all parameters or receiver. If they’re not used, only type can be specified. Name can be eventually introduced later on when it’ll actually needed:

type T struct {
name string
}
func (t T) F(name string) {}func (T) G(string) {}

Declaration of G omits useless identifiers.

--

--

Michał Łowicki
golangspec

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.