It’s been a long time since I played Monopoly* but I was reminded of it recently. The starting point of the game is the ‘Go’ square, and every time you complete a circuit of the board you pass ‘Go’ and collect a salary. With this magic ‘Chance’ card you can accelerate your journey, jumping straight to Go in a single step.
The less charitable of you will now be thinking that this was a poor attempt at coming up with a cynical, clickbait friendly heading to get people to read this article about our adoption of the Go language. The more charitable will think that this was a good attempt at coming up with a cynical, clickbait friendly heading.
Anyway, no accounting for taste…
The Next Generation
As many of you know I took on a new challenge last year, joining an Irish FinTech with strong growth ambitions. InvoiceFair have been around for a number of years and (thankfully) are very successful; the founders wanted to move in some new directions, and hence we met – I won’t be tiresome by dwelling on the details. Sufficient to say that I joined as CTO last August.
Exploring the ambitions of the company led to the genesis of our next generation platform that will combine many interesting technologies that would support the business in driving growth over the next phase of its development. There were any number of discussion on where our platform could actually make use of Blockchain technology, machine learning and AI. All pretty heady stuff, but grounded by a practical focus on actually getting this done.
Given that the flood gates were completely open to innovation at any level, the question of which language best suited our goals came up. We have a mixed bag of skills, with mine firmly grounded in .NET, others being in the Java camp and still more. I have to admit I wanted to explore, and fortunately the team were open to looking around.
If you’ve ever wondered how that StackOverflow Developer Survey actually helps progress technology, this is it!
The Hunt Begins
We started with what we had, and as strong advocates of each of these technologies knows both the .NET and Java worlds have plenty going for them – well supported, Object Oriented, vast libraries of available features and systems, increasingly verbose and a little heavy on the need for boilerplate code, losing their focus and direction as they try to find a place somewhere between OO and functional constructs and much like this author starting to look a little old, a little tired and truth be told a little bloated (I welcome all comments from you that offer to contradict this in any way).
The bottom line, you couldn’t push a sheet of paper between these two stacks, and for building a platform that would take us into the next five or more years they both appeared to be solid, but just a little, well, underwhelming.
So we looked further afield.
Python immediately appeared. Again well supported, a vibrant community, nice tie in to the Machine Learning world particularly, and a bit of a darling for the big data people. Obvious choice, and one not to be ignored.
To be honest, I quite like Python – it’s not the quickest thing in the world, but it is quick to get developers up to speed on using it, there’s a very active community which leads to a lot of material on dealing with the issues that arise particularly in using it in the cloud space (there do seem to be quite a few).
A strong candidate, but with one glaring problem: everyone wanted Python programmers and as a young, plucky company, we’re going to struggle to compete with the numerous established banks and insurers let alone the very active range of startups in Dublin, Ireland and further afield in Europe.
Of course, I also had a thing for playing with a functional language. With the rise of closures, lambdas (not the AWS flavour) and the increasing trend for languages to adopt functional paradigms, it seemed reasonable.
So F# was given a run. It’s nice, I liked it, and to be honest suffered too much with the mental shift required to want to try to build / subject a growing team to that kind of pain (did I mention my general curmudgeonly demeanour?). F# will be in my future, but not in InvoiceFair’s any time soon I’m afraid.
Back to this Go / Rust question. Both are modern enough languages, there’s plenty of buzz around them, they rank high on the list of the languages that people want to use and there’s a vibrant community. Well worth a look.
The Rust Journey
Rust is great, and if I could stop there, this conversation (well this post at least) would be over. Rust was like a shooting star, it rose quickly in our estimation, burned bright and then sadly faded fast.
There is so much going for Rust, that we were very excited to take a look. The tooling is good, the community is active, there’s plenty of material for most of the tasks you’d want to take on; in short there’s plenty to recommend Rust.
It just didn’t meet our needs. I’m going to speculate a bit here, and this may resonate with many of you, but we just felt that we weren’t the target market for Rust. It has the performance, it has the support, but it’s pedigree of solving the problems that C/C++ developers have been struggling with for a long time just wasn’t us.
We weren’t in an environment where C/C++ was the logical choice. We weren’t in the embedded space and the approaches we are taking to scale and availability are better met outside of eking the absolute maximum performance out of each (virtual) host. The constraints that Rust puts on developers are just too, well, constraining from the outset, leading to a rather frustrating experience. I’d still suggest looking at Rust, and would be interested in hearing whether the safety / performance vs early productivity trade off is something you experience.
We then turned our attention to Go, and we found our winner.
Go, much like Rust, has a passionate community of developers who have produced vast swathes of material to accelerate learning the language. There’s a wide range of libraries supporting many of the things you would want to achieve. We’ve done quite a bit work on our Blockchain implementation, our domain specific rules language and web services – both in containers and in the form of AWS Lambdas.
Go balances a compact language style with a clean syntax. There’s just enough OO style to be useful, but not so much that it results in bloated code. Package management is clean, and the tooling is good. Also interesting is of course that it produces native executables and whilst there’s still the overhead of the Go runtime, our experience to date when pushing that code into a container is that the image size is quite compact.
Go was developed with concurrency in mind from the outset leading to a nice clean method for spinning up parallel operations, called Goroutines, and channels for intercommunication. The documentation is at pains to point out that Goroutines aren’t threads, but for the sake of a mental shortcut, you can think of them as threads. For me, whatever about the rest of the team, wrapping my head around channels and the context construct was one of the most useful things I learned about the language.
It’s not all wine and roses though – this isn’t a parliamentary office party during lockdown after all. Go has a couple of idiosyncrasies that gave various people on the team pause for thought.
Go’s approach to error handling can be seen as anachronistic by developers who were brought up in a world where structured exception handling is the norm. In the old days, when dinosaurs roamed the earth and everything was a vaguely moribund shade of gray, it was quite common to make a function call and to check its return code for an error. This is what happens with Go.
There’s always been a healthy debate on the cost of structured error handling, both in terms of effectiveness – how many applications check for every exception – but also in terms of clarity. Unhandled exceptions keep bubbling up until something catches them. Whilst many capture the stack trace on the way, that doesn’t really do much for the developer in terms of recovery.
Anyway, it’s interesting that Go has avoided structured exceptions, mostly because it has the dual effect of forcing your code to handle errors local to where they arise (or ignore them), which is arguably good, and of bloating your code with lots of error checks in every function, which is a contradiction to the otherwise very clean style of the language.
Go doesn’t have classes per se. Naturally it does have a mechanism for defining types and interfaces, and it does allow methods to be associated with those types – so far so good. But much like the C++ of old those methods can associate with their host type by reference or by value, with value being the default approach.
Not unexpectedly, passing by value creates a copy of the value and works on that, whereas by reference sends a pointer. Again, the more, em, ‘mature’ programmers are familiar with these constructs fall into line with this approach relatively easily, but some of the young whippersnappers took a bit more time to get into this frame of mind.
In a similar vein to structured exception handling, generics can be a boon in the right hands. From the dawn of the Standard Template Library (STL) back in the day with C++, generics have worked their way into most mainstream general purpose languages. But not so in Go.
General OO techniques, with base classes / interfaces, allowed for the same effect as generics long before generics appeared, albeit with a bit more overhead. For advocates, the compile time checking that comes with Generics is a major plus, for the purists, generics are a short circuit and dilution of the inheritance based approach which, in a similar manner to advocates of Shakespearean english, is to be derided when not followed.
From a Go perspective, it is again an interesting omission as generics can lead to smaller code. I believe they are on their way though in 1.18, and can be explored in the beta program for that version. For my part, I do miss them, but haven’t found it to be a major impediment.
Conclusions – Should You Go to Go?
It has been noted on occasion that I can ramble on a bit, so I’ll draw some conclusions and wrap up.
Was Go a good choice. Quite simply yes, its a clean and elegant language that has a wealth of libraries and support available with a pretty active community of developers. The tooling is fast and the output is small and well supported on the platforms that we are targetting.
The learning curve is short, so we became productive quite quickly. We’re far from experts, but expertise is growing fast.
Java, C#, Python and Rust are all reasonable choices, but after close to 20 years of development in .NET, and slightly more in Java, I feel that those languages are in need of a bit of cleanup. Microsoft to their credit did quite a bit of this with the move to .NET core, but perhaps could have been a little more surgical in deciding on single approaches to problems. Java, for me anyway, feels increasingly in the wilderness; it was bolstered by the Android world for quite a while, but I suspect that the emergence of Kotlin may ring the death knell for that particular avenue.
Rust was a great choice, and if you feel it aligns with your overall project types, I would strongly recommend reviewing it for yourself.
This left Python, which was harder to dismiss. It has similar support, and works in similar environments to Go. To be honest it was close enough to be a bit of a coin flip, but given some of the ambitions we have for the InvoiceFair platform, we concluded that Go had the edge in areas of interest to us.
I’ll put some more information up as we move through our journey in the development of the platform. No doubt there will be highs and lows – it’s still software development after all.
*Monopoly, formerly a brand from Parker Bros. is now owned by Hasbro