3 August 2023

Alternative Go Method Expression Syntax

You think you know a language and then...BAM!

TL;DR

Click for spoiler

The following three Go method expressions are actually equivalent!

time.Now().String()             // 😎
time.Time.String(time.Now())    // 🤔
fmt.Stringer.String(time.Now()) // 🤯

mind == "blown"

I learned something new today about invoking methods in Go. This came about because of an interesting little bit of code I was reviewing with a coworker who is new to Go. They had written some code which, quite frankly, blew my mind because it didn't appear to be valid syntax based on my experience (yet it was clearly compiling and executing on the coworker's machine). Here's a simplified version of the code:

func Foo(v any) {
	pointer := reflect.ValueOf(v)
	if pointer.Kind() != reflect.Ptr {
		return
	}
	actualType := reflect.Type.Elem(pointer.Type()) // <- WAT!
	fmt.Println(actualType)
}

What really threw me off was the line right before the fmt.Println. I knew that reflect.Type is an interface definition, and that Elem() is a method defined with the Type interface, but the Go compiler in my head threw up all sorts of errors that went something like this:

"Hold it right there! You can't just directly invoke methods on interface definitions! (Can you?)"

...

"Wait a minute, even if you could invoke methods on interface definitions, the Elem() func is niladic--it doesn't receive any parameters!"

...

"Hold up, why have I never seen anything like this? How is this code even compiling, let alone executing!"

...

Ok, so after some research we learned that

And this has all been in the official spec forever:

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. (emphasis mine)

A Simple Example

Let's break this concept down with an example that doesn't involve reflection.

Consider the following Go type definition:

type Point struct { x, y int }

Now, a trivial method:

func (p Point) String() string {
	return fmt.Sprintf("(%d,%d)", p.x, p.y)
}

Suppose now that we had an instance of our type:

func main() {
	p := Point{x: 1, y: 2}
	...
}

Now suppose you wanted to print the instance instance by calling the String method defined previously:

fmt.Println(p.String())

This is all pretty standard, but did you know that you could accomplish the same method invocation in this way?

fmt.Println(Point.String(p))

Conversely, did you know that you can get to it via the fmt.Stringer interface (because our Point implements fmt.Stringer)?

fmt.Println(fmt.Stringer.String(p))

And that is the equivalent of the line that blew my mind from the original code snippet above:

actualType := reflect.Type.Elem(pointer.Type())

Hmm

In case you're having trouble accepting that all of this code even compiles, here's a Go Playground--have at it.

I'm really not sure why this alternate syntax exists, but I guess it's interesting, maybe because it hints at the underlying representation of types and methods. But what I'd like to know is whether it facilitates any scenario or solves some problem that the more traditional method expression syntax doesn't provide for?