It’s been about a year since I started using Go. I’ve written a lot of code with it, including an entire micro-service and a contribution to glide. I want to emphasize that, for the most part, it’s been pretty good. But about once a month, my eye starts twitching and I get the urge to start making a head-to-desk motion. The reasons for this can be pretty simply demonstrated. Since I seem to have this conversation on a regular basis, I thought I’d record my thoughts so I can just simply reference this the next time it comes up.

A Simple Data Structure

I need a data structure. It has the following requirements:

It must store the name of an animal and an associated struct containing some information regarding the animal. It must have O(1) lookup time. I must be able to iterate over it’s elements in some predefined order. It must be immutableÂ¹.

At first glance, we might be tempted to simply use a map like so:



type AnimalMap map [ string ] Animal

This is kinda nice. In fact, it satisfies the first two requirements of our data structure. But since the very early days of Go, the order objects are stored in a map is random (for good reason). So I can’t do something simple like insert all the elements into the map in the same order over which I’d like them to be iterated.

Alright, no big deal. We can get around this by just writing a little bit more code, using an array to keep track of order.



type AnimalMap struct { Mapping map [ string ] Animal Order [] string }

Now whenever we want to iterate over the map, we need to iterate over the Order slice instead, using the strings we pull off as keys into the map. Not the greatest, but it works.

What About Immutability?

This is where things get really hairy. Go doesn’t have the concept of runtime constants. If we want things to be immutable after we create them, we’re going to have to completely, and I mean completely, encapsulate both the slice and the map in our struct. This is because maps and slices have pass-by-reference semantics. In other words, if at any point I give you a reference to them, you’ll be able to modify them. The best I can come up with is now this explosion of code:



type Animals interface { Len () int At ( i int ) string } type animals [] string func ( a animals ) Len () int { return len ( a )} func ( a animals ) At ( i int ) string { return a [ i ]} type AnimalMap struct { mapping map [ string ] Animal order animals } func NewMap ( order [] string , mapping map [ string ] Animal ) AnimalMap { newMap := AnimalMap { mapping : make ( map [ string ] Animal , len ( mapping )), order : make ( animals , len ( order )), } for i , name := range order { newMap . mapping [ name ] = mapping [ name ] newMap . order [ i ] = name } return newMap } func ( am AnimalMap ) Order () Animals { return am . order } func ( am AnimalMap ) Animal ( name string ) Animal { return am . mapping [ name ]}

What just happened? That’s an infuriating amount of code to write. You know how much code I’d have to write if this were C++, C#, or Java? None. They all have reusable notions of an immutable, ordered map. Yet in Go, every time I need one I have to write 30 lines of code. I feel like I had to fight to Go every step of the way here. I feel sad.

Conclusion

Go has some pretty neat concepts. I love that it enforces composition over inheritance. Goroutines are pretty nifty. I’ve been pretty satisfied with it overall. But time and again, I find myself solving the same problems over and over when using Go. I find myself completely blocked, with no way to write DRY codeÂ².

I’d love to see the Go community address issues like this. IMHO generics would solve all of the issues above, but I understand that’s somewhat of a sore topic for the Go community. Perhaps something like a simple form of codegen for common data structures and algorithms?

Kurtis Nusbaum is a Mobile and Backend Developer at Uber. He’s an avid long distance runner and diversity advocate. Here's his mailing list. Here's his Twitter, and LinkedIn. Here’s his github. If Kurtis seems like the kind of guy with whom you’d like to work, shoot him an e-mail at kcommiter@gmail.com.

Â¹Here's why you might want immutability

Â²For instance, try writing a reusable exponential backoff algorithm or a reusable binary tree. Oh, and you can’t use interface{}. We’re programming in a statically typed language for a reason.