Contents

Golang method expressions

What are method expressions?

Coming from a C++ background, I’ll allow myself to use a C++ example. If you know C++, Golang’s method expressions are very similar to member pointers. The code is relatively simple and even if you’re not a C++ enthusiast it should be possible to understand the intentions

Here’s a short C++ recap:

 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
class Foo {
public:
    std::string foo() {
        return "foo";
    }

    int bar(int i) {
        return i;
    }
};

using FooPtr = std::string (Foo::*)();
using BarPtr = int(Foo::*)(int);

int main(int argc, const char *argv[])
{
    Foo f{};

    FooPtr fooPtr = &Foo::foo;
    BarPtr barPtr = &Foo::bar;

    std::cout << (f.*fooPtr)() << std::endl;
    std::cout << (f.*barPtr)(123) << std::endl;
    return 0;
}

In C++, member pointers allow to obtain a pointer to either a member function or a variable. The pointer itself is disassociated from any particular class instance. Thanks to that, member pointers were often used as delegates. Nowadays these are superseded by lambdas or std::bind although the usage of the latter is rather discouraged as well. Right, but this is a golang post, isn’t it? Here’s how method expressions work in golang:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
type T struct {
}

func (t *T) foo() string {
	return "foo"
}

func (t *T) bar(i int) int {
	return i
}

func main() {
	t := &T{}

	foo := (*T).foo
	bar := (*T).bar

	fmt.Println(foo(t))
	fmt.Println(bar(t, 123))
}

The expression

(*T).bar

yields a function of signature:

func (t *T, i int) int

The language specification goes into more details here.

Is this a syntactic sugar?

Quite right, it is. It’s similar to having an anonymous function, formed the following way:

1
2
3
bar := func(t *T, i int) int {
    t.bar(i)
}

This is of course tedious and a bit manual.

Why is this useful?

Personally, I’ve used method expressions to form a dispatching table/map. Consider the following:

 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 T struct {
}

func (t *T) foo() string {
	return "foo"
}

func (t *T) bar() string {
	return "bar"
}

func (t *T) baz() string {
	return "baz"
}

type Delegate[T any] func(*T) string

func main() {
	t := &T{}

	m := map[int]Delegate[T]{
		0x01: (*T).foo,
		0x02: (*T).bar,
		0x03: (*T).baz,
	}

	fmt.Println(m[1](t))
	fmt.Println(m[2](t))
	fmt.Println(m[3](t))
}

Thanks to such approach the execution can be driven by data. This is especially useful when writing i.e. an emulator. In the above example, I’ve allowed myself to use Golang generics to define a delegate type… but that’s a topic for a completely different post.