This is a story about the good and the bad sides of the Go programming language, about it being a time for a change, and about how carefully such a change must be handled. This is the first part of a 3-article series. In this piece, we’re going to set the scene and discuss why, for many use cases, Go can be the best choice. 💡⭐
In This Series
- What Makes Go the Best Language
- We Need To Talk About The Bad Sides of Go
- A Proposition For a Better Future
The Go programming language is exploding with growth these last several years. Big companies, as well as small start-ups warmly adopt it, it’s getting as close as it can to being a standard language for cloud-native systems. It’s also been the most desired language to learn for several years in a row (HackerRank study, 2020) and even the fastest growing language (according to OSS study over GitHub repositories, 2022).
Us Gophers, we absolutely love this language…but we also constantly wish to change it (🤔🤷🏽♂️?). There are so many open GitHub issues containing language change proposals, so many online discussions, and the grand finale in the shape of the recent introduction of generics, the biggest language change up until now. A change that divided the community in two — those who warmly embrace it, and those who believe this will forever change the language.
The Go community and its maintainers tend to be very consistent regarding language proposals. As they should. It goes hand in hand with the language’s most basic principle, and as a Gopher, you must agree with it at some level — it’s all about simplicity and readability.
The Breaking Point
Generally speaking, we all agree that simplicity should be highly valued. Code should be easier to read than it is to write. How can you argue with that logic? Every professional developer knows that you write a line of code once and then maintain it for 5 years. In a very permissive language, you may be tempted to write that line of code in a very fancy way, and then everyone else is scratching their head trying to figure it out. Including your six months older self.
You could say we all agree that simplicity is important. However, simplicity comes at a scale, and at some point, you start trading off productivity. 📈📉 This is where Gophers differ. Some Gophers, maybe even most of us, believe that the language today is where it should be. Perhaps even prior to the arrival of generics. Any further addition will hurt readability, productivity, and maintainability. 😵💫
Other Gophers believe the language should support new features. There’s a huge list of language proposals for features aiming to enhance productivity and reduce ceremony and code duplication. Most of them get rejected for simplicity’s sake. In a recent episode of the Go Time podcast, Kris Brandow referred to this exact breaking point saying “I think it’s time for Go to have a fork”, this will allow keeping the language as it was originally designed while allowing an extended flavor of the language to evolve. 💡
If such an event is to take place, I believe it’s a critical moment for the Go community. I believe designers of such a new dialect must be responsible enough to adopt some proposals and features but retain Go’s core values. This will determine whether Go and such a fork can seamlessly interact, and can share the same runtime environment, libraries, and ecosystem.
This kind of responsible decision-making is important to ensure we remain a single community with a single ecosystem, believing in the same core philosophy, while offering an alternative flavor, allowing sacrificing some simplicity to gain maintainability and runtime safety. 🛡️👷
I also believe this is the right move for us Gophers. But why do I think that? And why do I think Go is a perfect candidate for such a secondary dialect? We’ll slowly work our way to a full answer in this and the following article, discussing the good and bad sides of the language.
What Am I Fighting For?
So the first question that pops to mind is — if you don’t like Go, why aren’t you switching to a different programming language? A one that supports more features and more expressive syntax. I’ll answer this question from a personal point of view, but I believe this answer also captures the sentiment of many other Gophers.
This is very fundamental to Go’s philosophy, and I believe it to be one of the most powerful ideas in coding in general.
1 for i in input().split():print((str(bin(int(i,16))[2:].zfill(4))+str(bin(int(i,16))[2:].zfill(4))).replace("0"," ").replace("1","X"))
And a claim that writing fewer lines of code should generally be a goal. I’m not into shaming any particular developer, instead, I’d like to argue that this is the result of the coding culture of the community. Obviously, this is not necessarily mainstream to any language, but it is widely used. I believe we can all agree that this kind of coding can very quickly become less readable, less maintainable, harder to debug, and sometimes even hurt performance. Go’s readability-over-writability culture encourages the opposite, and you’ll very rarely stumble across such styling in Go codebases, nor Gophers advocating stuffing as much logic into as few lines possible.
I’m well aware that this particular example and many others are just a fun exercise, but I also know that these habits tend to make their way into real codebases. I’ve sinned myself, and I’ve seen many others do too. Sometimes it’s just fun, other times it seems “elegant”, or just too tempting to avoid.
Us Gophers, we love throwing ‘native concurrency’ to the air, but Go didn’t invent anything other than designating a language keyword for spawning new threads. Most other languages support spawning new threads with one line of code and no external dependencies. Is Go really more native in that regard? Also, We like to mumble about goroutines and channels, but those too, are not that special. Channels are simply blocking queues, and goroutines are just a spin-off of coroutines, a concept that’s been around since the 50s. 😱
So what is special about Go’s concurrency model? Quick answer — of all popular languages, Go has the most consistent concurrency model. In fact, it’s the only major language with a fully consistent concurrency model. Remember what we said before about not having more than one way of expressing something? Well, each of the following languages has at least two ways of expressing and managing concurrency: C, C++, C#, Rust, Python, PHP, Ruby, Java, Kotlin, and Scala. They all have both blocking and async APIs.
In most cases, if you go for the blocking API you will not be able to fully utilize your resources. This means you’re either too slow or too expensive. Or both.
To avoid that, modern applications tend to use async APIs, but a ton of problems and caveats immediately join the mix. First of all, it’s not always very clear whether a function or a library is, in fact, async. The compiler can’t help you there so you have to rely on documentation. If the doc doesn’t make it clear, you find yourself digging into the source code. 🥴🔫
Okay. Enough about other languages, let’s talk about Go. Its runtime, to me, is by far the most powerful and reliant for building scalable and concurrent applications. As I said, Go is the only language in all the languages mentioned above whose concurrency model is fully consistent. How so? Well, it only provides a blocking API. Or should I say it only provides an async API? Because the syntax is always blocking. You never need to suspend, await or pass a callback. You call a function and it returns a value. This is as clear and readable as if no I/O operations are involved at all. However, under the hood…everything is completely async. 🚀 Go runtime achieves that by implementing coroutines, or as they’re called in Go — goroutines. In most languages, spawning a new thread results in an allocation of a new OS thread. OS threads are not cheap. In addition to consuming resources, context switching between them is highly expensive. When you use those to perform blocking operations you actually consume a lot of resources to sit down and do nothing while waiting for an I/O response. Go runtime does it differently. It lets you spin up as many goroutines as you like, internally they’re completely independent, just like regular threads, but externally, Go allocates just the amount of OS threads it actually needs, and with those, it only performs async system calls. Spin up thousands of threads in other languages and you’ll suffocate most machines. Spin up millions of goroutines and you should be fine. Try it out, it’s a very simple test to perform.
Powerful stuff huh? The code is simple, the performance is optimized. 💪 But hang on, there’s more. Go is not the first language to use coroutines or any kind of virtual or lightweight threading. However, Go is the only one of the above-mentioned languages that had it right from the get-go, and that never switched. The results in a single, fully consistent model — no matter which library you use, what framework you’re comfortable with, they all share the same model, and the most powerful one. 🤯💥 Even if Go is to add support for promises at some point — it will merely be a new way to track the execution of running threads, it will not require changing the shape, signature, or the behavior of called functions in any way. This is simplicity. This is confidence. This is powerful.
Oh, and it’s also very fast compared to other garbage-collected languages.
Community and Tooling
Lastly, Go is not shy of any other language in regards to community and tooling. This is a very careful statement, a lot of Gophers, including this one☝️ believe that some aspects of those are superior compared to the competition. Built-in support for profiling, code formatting, data race detection tools and benchmarking tools, shipped alongside the compiler and a package manager with no centralized registry are all examples of such. I deliberately avoid glorifying those because there are pros and cons in each tool and most languages have their own neat set of tools and rich communities. However, Go makes a strong competition in that regard, to say the least.
Gophers also like to mention that Go is easy to learn, it’s scalable, it has an active community, it’s fast, highly demanded, garbage collected, it has fast build times, it produces small, single-file binaries, and it can run in many different environments. I strongly agree with most of these points, and there are many articles out there that will reveal more interesting features of Go. I wanted to focus on the special powers that make Go unique and superior to other languages.
In this article, we’ve discussed the state of the Go community and the calls for change and presented the stronger sides of Go — what makes it a special language and what makes its runtime a uniquely powerful environment for writing and running scalable and reliable applications.
In the following article, we’re going to discuss the weak sides of the language — the main focus areas that make Gophers demand a change.