Method expressions

October 30, 2023

There was no livestream today, as I was a bit under the weather. I intend to be back live streaming next Monday as usual

Method expressions

If M is in the method set of type T, T.M is a function that is callable as a regular function with the same arguments as M prefixed by an additional argument that is the receiver of the method.

MethodExpr    = ReceiverType "." MethodName .
ReceiverType  = Type .

This might seem a bit mundane at first. But there’s something subtle, going on here, that surprises most people when they first encounter it.

Consider a struct type T with two methods, Mv, whose receiver is of type T, and Mp, whose receiver is of type *T.

type T struct {
	a int
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver

var t T

The expression


yields a function equivalent to Mv but with an explicit receiver as its first argument; it has signature

func(tv T, a int) int

If you’re like most, you’re so accustomed to calling methods on instantiated values, that you’ve never considered what the full function signature of a method looks like when separated from the receiver—or that the concept even makes sense.

But that’s essentially what we’ve done here:

x := T.Mv

notMv := func(T, int) int {
	return 0
x = notMv  // This assignment is valid, because notMv is of the same type as x, aka `func(T, int) int`

That function may be called normally with an explicit receiver, so these five invocations are equivalent:

T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)

Tomorrow we’ll complete the discussion of the second half of the example, with the pointer receiver.

Quotes from The Go Programming Language Specification Version of August 2, 2023

Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content

Method expressions, part 2

Today I’ll continue where I left off yesterday, talking about… Method expressions … Similarly, the expression (*T).Mp yields a function value representing Mp with signature func(tp *T, f float32) float32 The key difference to notice between this example and yesterday’s is that the first argument (that of the receiver) is a pointer here, but was not a pointer in the previous example: func(tv T, a int) int // First argument of type T, not a pointer func(tp *T, f float32) float32 // First argument of type *T, a pointer For a method with a value receiver, one can derive a function with an explicit pointer receiver, so

Method values

A quick reminder: In today’s live stream, I’ll be deploying a Go app to Kubernetes! I hope you’ll join me! Today we continue the explanation of Method values. There’s nothing too surprising today. The spec mostly just confirms what should seem natural… Method values … As in the discussion of method expressions above, consider a struct type T with two methods, Mv, whose receiver is of type T, and Mp, whose receiver is of type *T.

Type parameters in method definitions

Yesterday we saw that when a method is defined on a generic type, the receiver must include type parameters. Now for all the relevant details: Method declarations … … Syntactically, this type parameter declaration looks like an instantiation of the receiver base type: the type arguments must be identifiers denoting the type parameters being declared, one for each type parameter of the receiver base type. The type parameter names do not need to match their corresponding parameter names in the receiver base type definition, and all non-blank parameter names must be unique in the receiver parameter section and the method signature.