Methods in Go (part II)
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
Click ❤ below to help others discover this story. If you want to get updates about new posts and boost my motivation to release new resources please follow me.