Why do I write GoLang in 2021?

Michael Francis
6 min readApr 22, 2021

TL;DR — it does what I need to be productive, with enough expressiveness and performance for almost any task I need.

I was out walking our dogs, and my then fourteen-year-old son (*) asked me what seemed like a simple question — ‘what language do you code in at work?’ I told him today I mostly code in GoLang. He looked back at me blankly, and I rambled off something about ‘it’s a descendent of C.’ Interrupting me, “but, why GoLang?” To understand my answer, you have to start at the beginning.

(*) He was taking a game design course at the time, utilizing C++.

Possibly the first programming language I used was BASIC on the ZX81, typing code from magazines and books to do incredible things in 1K. I spent most of that time typing mistakes and having to go back over and over, figuring out what I had done wrong. The final results were magical to a ten-year-old. I could play games, and the computer would do what I asked it to do. If I wanted to push the envelope, then it was out with the cassette player, and I could load a flight simulator in less than 16K if the ram pack didn’t shift.

I then rapidly passed through various BASIC dialects from Acorn Computers and a very brief ‘forced’ interlude (at university) in Pascal. By that time, I had already picked up C and quickly convinced my professors to submit my course work on C vs. Pascal. I’d also had the fortune to have friends that were into the ‘demo’ scene on the Amiga. We partnered on projects together, writing assembly code for a variety of microcontrollers. Somehow, being able to visualize what a computer does from the logic gates and up always helped.

Post-graduation, a lot of the work was more natural to accomplish in the then relatively new Perl5. When I last checked, those Perl scripts continue to run, and they may still be. Most of the hard-core programming was C. The cornerstone of our system was an ‘object’ system, which encapsulated the behavior of components behind method dispatch. Today we would likely describe this as an Actor model with a job scheduler. The objects had defined life cycles and composable functionality; what’s old is new, I guess?

Then along came C++ with all the promises of built-in classes and inheritance. I transitioned to code almost entirely in C++ quickly, fighting the good fight against tools like ‘rational rose and the plethora of design patterns. The early days of templates in C++ gave us a type-safety and expressiveness that allowed us to remove the void* dispatch loops and build high-performance collections. In some ways, these were the heyday for C++, fast compile times, understandable error messages, and strong type safety. Then came the STL, almost overnight, a string was no longer a pointer to a char*, the stream abstractions became generic but at the same time losing the simple grok-ability they had in the past. Then came along Java, starting its upward path to ascendancy in the enterprise.

I spent a couple of years solely hacking in Java with a brief sojourn in C#. I learned a bitter lesson around the reification of generics and the size of the duplicated code in Java vs. C#. Coming to code from others and being forced to understand why they had implemented a visitor pattern with a facade and a factory function when there would only ever be one implementation of the data traversal never ceases to boggle my mind. It’s not an intrinsic fault of the language, and you can write excellent, fast, clean Java code that rivals most other languages — the problem is that many do not.

Throughout this time, there had been the rise of the web UI, starting with CGI — I wrote a CGI script that wrapped our C object system and allowed direct access on the web — it worked well, but we were not ready for it. Over time, JavaScript replaced CGI, and the modern web ascended — I have plenty of thoughts about JavaScript, both good and evil; those will be the topic of another post.

Bringing us back to Go, Go feels like a reset to the days when C++ was reasonably new; it had a small surface and, in general, behaved the way expected. C++ didn’t have many modern features that we come to expect, garbage collection, a batteries-included standard library, module systems, and an opinionated build system. These are the areas where Go excels. The language is concise, and it behaves the way you expect it to, excepting the pointer interface duality **

(**) Pointer interface duality — a painful experience in Go where you use pointers to objects but references to interfaces. Then you get tied up in whether a type should have pointer receivers or not. It makes sense from a runtime perspective, but unless you come from a C/C++ background, it is surprising and can lead to some fundamental head-scratching sessions.

I find people who have spent a lot of time using generics/templates, myself included, struggle to grasp that you should just be using Go interfaces. With careful thought, one can often find a solution that uses interfaces to solve the problem more cleanly. Unfortunately, it is not always the case, and I feel like I’m back in C void* downcasting as I reach for the reflection toolbox. It’s Go, so it is type-safe, but it has that slight code smell.

I’m generally happy to see the Go community add generics to remove the map built-in and associated functions. The end state is having these implemented natively in the language vs. the runtime. I am concerned this will lose the Go’ness of the language. A language that fully opened my eyes to how potent generics and macros could be was Julia; in Julia, even the essential functions on integers and doubles derive from libraries written in Julia. Mind-blowing when you first read the bootstrap code. Hence, a user-defined type is no different from the types provided by the runtime. This ability to transparently extend the type system allows an expressiveness that I have yet to replicate in other languages.

GoLang is a game-changer because it feels like a scripting language, the compile cycles are fast, and with go run <go file>, I can iterate exceptionally fast. The tooling overall is first-rate. The new module system gives confidence in using third-party libraries, and go-format, go-test, and go-perf assert that the code quality is high. The type system eliminates many of the gotchas of untyped scripting languages; in general, if it compiles, it runs without throwing runtime exceptions.

As with many newer languages, I struggle with running them behind corporate firewalls, and building the controls required to map to our third-party code policy takes time. Where is the simple reference implementation of go-sum and go-proxy that run locally? We have solved these issues and have production code deployed, but it was more complicated than it should have been.

What sets GoLang apart from the scripting languages is the runtime performance. In real-world cases, we see the elapsed time being close to (or beating) optimized Java code without spending significant time on tweaking. Worst case, Go generally appears to be ‘fast enough.’ When it comes to deployment, Go is a game-changer. The single executable with all dependencies guaranteed to run on the target platform can’t be understated. Java does an excellent job of this, provided you have the right JRE installed (a challenge across large fleets of servers); you only have to manage a single jar file. Python and the other scripting languages leave a lot to be desired and are generally easier to deploy as a docker image, with the added layers that that entails.

One of the domain-specific performance tests I wrote was a simulation of a randomly generated portfolio of instruments with a representative discount curve-based pricing model. Pricing a portfolio is a naturally parallelizable problem; by mapping to goroutines and channels, I achieved near-linear scaling across cores and clock rate.

So coming full circle, I choose to use golang because today I find it the sweet spot, it meets my needs, the type system is rich, has a fast learning curve, and has some of the best support for linear scaling compute using channels and goroutines. I miss functional forms from other languages, though these will either become available through the generics proposal or have an idiomatic alternative in golang. I do get frustrated with writing all those for loops.

My son looked at me and said- “I get it, so why is there not just a single programming language?” That’s a topic for another time.

This story was first penned in early 2020, updated, and published in 2021.

--

--