Switching on custom types in Golang

Last week I ran into an unexpected behaviour in Go regarding type switching. In the end, it makes perfect sense but the initial reaction was complete confusion.

The algorithm in question needed to perform different types of actions based on incoming messages. Each message could provoke different number of reactions which could take form of different data structures or operations to be performed later. Message handlers therefore returned a slice containing either values of struct A, B or C, or an anonymous function. The slice was later iterated over and type matched. Each struct type was handled appropriately and the anonymous functions were fed arguments and ran.

That was the idea at least and it would work ok (even though it’s not a very Go thing to do and I eventually refactored the code in question to use another approach) if I hadn’t created a type alias for the anonymous function type. It had several arguments so it was pretty long and naming it made it easily understandable in the switch. Another thing named and the code is easier to grok, perfect!

Well, not exactly because the functions were never being type-matched and the switch fell into the default branch. How is that possible? It took me a while to figure it out, but the situation can be boiled down to the following code:

// Alias string type
type MyString string

// Alias func
type MyIntToIntFn func(int) int

// Switch on some built-in types and the newly defined types
func printType(x interface{}) {
	switch x.(type) {
	case int:
		fmt.Println("Integer:", x)
	case bool:
		fmt.Println("Bool:", x)
	case string:
		fmt.Println("String:", x)
	case func(int) int:
		fmt.Println("Fn int -> int:", x)
		
	case MyString:
		fmt.Println("MyString:", x)
	case MyIntToIntFn:
		fmt.Println("MyIntToIntFn:", x)
		
	default:
		fmt.Println("Who cares?", x)
	}
}

func main() {
    // Built-in types behave as expected
	printType(123)
	printType(true)

    // Default branch works as expected
	printType(456.789)

    // Let's get funky
	printType("Normal string")
	printType(MyString("Special string"))

	fn := func(a int) int {
		return a * 2
	}
	printType(fn)
	printType(MyIntToIntFn(fn))
}

Playground version

Try to think about what you expect to see printed before you actually run the code. I can wait.

Did you get it right? I bet you did. Great job. Now, where was I going wrong?

My handler functions returned a slice of interfaces []interface{} an in them an equivalent of func(int) int. The anonymous functions were never explicitly typed as MyIntToIntFn but I tried to match them as such.

For some reason I expected a value which can be converted to a custom type to match as that type. Strangely, I wouldn’t expect string to match as MyString (even if string case was not above MyString case) but it seemed absolutely natural for a function type alias at the moment.

As usual, the problem was my expectations — most likely coloured by other languages. However, while digging into this I realized that there are situations when my expectation would be correct:

"string" == MyString("string") // true

This automatic conversion seems a bit at odds with the behaviour of a type switch. It’s by no means a problem but if you happen to know, why Go behaves this way, let me know.

We're looking for developers to help us save energy

If you're interested in what we do and you would like to help us save energy, drop us a line at jobs@enectiva.cz.

comments powered by Disqus