Go Lang Impressions after 2 Months

So for work I've needed to learn GoLang, which I'd always before picked up, kicked the tires and said "meh" on. A few months now of working with it pretty-much-daily have upped the "meh" quite a bit. To be clear, I'd choose Go over raw C, but I'd likely pick modern C++ over Go.

Channels and spaghetti...

I get it, message passing is nice, and channels rigorously applied mean you don't need to worry about shared state. The problem is in the practice. What you wind up with is 99 tiny goroutines each of which is handling a tiny hand-off problem, lots of temporary channels implementing rpc-in-a-long-running-goroutine, etc. Where does this message I'm sending go? Why is the whole app stalled? Go fish. Maintenance load matters, and this stuff is really annoying to debug, particularly if you're not the one who wrote it.

We used to have a similar problem with Twisted Python code where you'd have dozens or hundreds of callbacks registered to a bunch of inter-dependent deferreds and you'd have to untangle the whole mess to figure out the flow-of-control. In Twisted that gave rise to using something that looks a lot like Python's asyncio (async/await), defining what looks like imperative code where an await-alike suspends current code until the result comes back.

Lack of opaque/meta-types...

One of my Open Source projects is pydispatcher, essentially an in-memory pubsub that provides generic publish/subscribe operations, where a channel (string) can be used to broadcast messages (opaque) to any number of subscribers. It's a common, useful, pattern for creating loosely-coupled components that can dynamically decide whether to subscribe to a given channel.

In Python, everything is an object, so the opaque data-structure is "PyObject". In C++ it's a pointer. In both, the conventions of the application mean you only send messages of the correct type to the channel. In C++ you can even make the whole thing templated so that each channel has a type.

In Go... you are expected to write a new pub-sub implementation for each message type? At least, that seems to be the idea. There are pubsub implementations for go, but they all seem to based on the idea of using a string as the opaque payload (i.e. not usable for efficient code). Maybe 1.18's generics will address this, but stuck on 1.17 this is a pain-point for me.

Lack of Syntactic Map Support

I've programmed extensively in C, C++, Java, Go, Javascript and Python along with using a half-dozen other languages. Languages should have built-in mapping types. Steal Python's if you want a good one. So many algorithms just roll off the keyboard with a proper, performant hash-map able to handle opaque data-values on the keys and values. Bonus points if the keys don't get converted to strings (looking at you JS).


x := {}(int,V)
for _,v : range somearray {
x[v.id] = v
}
return x

should be a thing you can do to get a quick index of your data-set. Yes, I realise you can do it in Go with make() etc. My point is that the unnecessary bookkeeping gets in the way of programming. Your Go compiler can spend the 5us to rewrite the synactic construct `x[v.id] = ` to `x.Set`. I get that everyone is afraid of spending 1/1000th of a second more compiling but meh, I'd rather not spend 10 extra seconds typing and let it.

Arrays and Slices

Similarly, a language should have clean, elegant syntax for handling arrays of opaque objects. Under the covers your compiler can check and see if it can optimise the array to only handle type X, but I should be able to easily add items to a list, sort, slice, etc.


x := [](Moo,V)
for _,v := range blah {
x.Append((v.moo,v))
}
x.Sort()
return [item[1] for item in x]

You shouldn't have to constantly re-assign the array-head-pointer. Map and Filter should be provided as methods on the list/array. Nice if negative indices let you slice-from-the-end, but not critical. List comprehensions are again, nice, but not critical.

The point in both map and list case is that these things are used all the time, they are part of almost any algorithm you want to write. If they are easy-to-use and syntactically supported you can describe algorithms concisely; the way the human thinks of the algorithm and the way the algorithm looks on the screen are essentially the same.

Stability/Production Ready-Ness

Memory leaks seem to be happening pretty frequently, some of those from C wrapper code (e.g. the Gstreamer wrappers), some from dangling channels.

Segfaults are pretty common during development, particularly in test code where you are testing one bit of functionality and didn't initialise the whole application stack. Mock setup is a PITA, requiring refactors of code to be interface/implementation based rather than allowing hands-off testing.

Code bloat; it takes a huge amount of code to do even very simple things when you are accustomed to a high level language. It doesn't feel much less verbose than C++.

The positives so far:

* performance is fine (this is 90% non-performance sensitive stuff, so not surprising)
* deployment is good, single-file executables with embedded resources is good (modulo it not working for one of our font resources)
* cross-compilation works well (once the system is setup for it)
* tooling in MSCode is good
* abstraction level is higher than raw C

Anyway, 2 months in, that's the impressions of a nearly-27-year Python programmer on GoLang. It seems I'll be doing a lot more Go coding, so I suppose I'll update these as my impressions evolve.

Comments

Comments are closed.

Pingbacks

Pingbacks are closed.