Methods in Go (part II)

Michał Łowicki
golangspec

--

This story explains remaining content from language specification touching methods. It’s strongly advised to read 1st part of the introduction.

Method expressions

Having type T whose method set contains method M, T.M yields a function with signature almost as in method M. It’s called method expression. The difference is additional first parameter of type equal to type of M’s receiver:

package mainimport (
"fmt"
"reflect"
)
func PrintFunction(val interface{}) {
t := reflect.TypeOf(val)
fmt.Printf("Is variadic: %v\n", t.IsVariadic())
for i := 0; i < t.NumIn(); i++ {
fmt.Printf("Parameter #%v: %v\n", i, t.In(i))
}
}
type T struct{}func (t T) M(text string, number int) {}
func (t *T) N(map[string]int) {}
func main() {
PrintFunction(T.M)
PrintFunction((*T).M)
PrintFunction((*T).N)
}

output:

Is variadic: false
Parameter #0: main.T
Parameter #1: string
Parameter #2: int
Is variadic: false
Parameter #0: *main.T
Parameter #1: string
Parameter #2: int
Is variadic: false
Parameter #0: *main.T
Parameter #1: map[string]int

Expression T.N would causes an error invalid method expression T.N (needs pointer receiver: (*T).N) since method N is not in method set of T.

One interesting case is PrintFunction((*T).M) from the snippet above. It creates function with first parameter of type *main.T even so the method M has value receiver. Go’s runtime under the hood indirects passed pointer, creates copy and passes it to the method. This way method doesn’t have access to the original value:

type T struct {
name string
}
func (t T) M() {
t.name = "changed"
}
func (t *T) N() {
t.name = "changed"
}
func main() {
t := T{name: "Michał"}
(*T).M(&t)
fmt.Println(t.name)
(*T).N(&t)
fmt.Println(t.name)
}

output:

Michał
changed

Method expression can be created out of the interface type:

package mainimport "fmt"type T struct {
name string
}
func (t T) M() {
fmt.Println(t.name)
}
type I interface {
M()
}
func main() {
t1 := T{name: "Michał"}
t2 := T{name: "Tom"}
m := I.M
m(t1)
m(t2)
}

output:

Michał
Tom

Method values

Similarly as with types and method expressions, having an expression it’s possible to get a function with receiver bound to that expression — method value. Having an expression x, x.M is a callable taking the same arguments as method M. Of course M needs to be in method set of x’s type or x is addressable and M is in the method set of &x’s type:

type T struct {
name string
}
func (t *T) M(string) {}
func (t T) N(float64) {}
func main() {
t := T{name: "Michał"}
m := t.M
n := t.N
m("foo")
n(1.1)
}

Promoted methods

If struct contains embedded (anonymous) fields then promoted methods are members of type’s method set:

package mainimport "fmt"type T struct {
name string
}
func (t T) M() string {
return t.name
}
type U struct {
T
}
func main() {
u := U{T{name: "Michał"}}
fmt.Println(u.M())
}

It’s a completely valid Go program which outputs Michał. There are certain rules when methods from embedded fields are part of encompassing type’s method set:

#1

When struct type U contains embedded field T then method sets of S and *S contain promoted methods with receiver T. Additionally method set of *S contains promoted methods with receiver *T.

package mainimport (
"fmt"
"reflect"
)

func PrintMethodSet(val interface{}) {
t := reflect.TypeOf(val)
fmt.Printf("Method set count: %d\n", t.NumMethod())
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("Method: %s\n", m.Name)
}
}
type T struct {
name string
}
func (t T) M() {}
func (t *T) N() {}
type U struct {
T
}
func main() {
u := U{T{name: "Michał"}}
PrintMethodSet(u)
PrintMethodSet(&u)
}

Output is as follows:

Method set count: 1
Method: M
Method set count: 2
Method: M
Method: N

What should be known from the first part of the introduction is an additional calling rule from language’s specification:

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

Because of that we can call u.N() even so the method N is not a part of method set of type U.

#2

When struct type U contains embedded field *T then method sets of S and *S contain promoted methods with receiver T or *T:

type T struct {
name string
}
func (t T) M() {}
func (t *T) N() {}
type U struct {
*T
}
func main() {
u := U{&T{name: "Michał"}}
PrintMethodSet(u)
PrintMethodSet(&u)
}

prints:

Method set count: 2
Method: M
Method: N
Method set count: 2
Method: M
Method: N

--

--

Michał Łowicki
golangspec

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