I strive to respect everybody’s personal preferences, so I usually steer clear of debates about which is the best programming language, text editor or operating system.
However, recently I was asked a couple of times why I like and use a lot of Go, so here is a coherent article to fill in the blanks of my ad-hoc in-person ramblings :-).
I have used C and Perl for a number of decently sized projects. I have written programs in Python, Ruby, C++, CHICKEN Scheme, Emacs Lisp, Rust and Java (for Android only). I understand a bit of Lua, PHP, Erlang and Haskell. In a previous life, I developed a number of programs using Delphi.
I had a brief look at Go in 2009, when it was first released. I seriously started using the language when Go 1.0 was released in 2012, featuring the Go 1 compatibility guarantee. I still have code running in production which I authored in 2012, largely untouched.
Go code, by convention, is formatted using the
gofmt tool. Programmatically formatting code is not a new idea, but contrary to its predecessors,
gofmt supports precisely one canonical style.
Having all code formatted the same way makes reading code easier; the code feels familiar. This helps not only when reading the standard library or Go compiler, but also when working with many code bases — think Open Source, or big companies.
Further, auto-formatting is a huge time-saver during code reviews, as it eliminates an entire dimension in which code could be reviewed before: now, you can just let your continuous integration system verify that
gofmt produces no diffs.
Interestingly enough, having my editor apply
gofmt when saving a file has changed the way I write code. I used to attempt to match what the formatter would enforce, then have it correct my mistakes. Nowadays, I express my thought as quickly as possible and trust
gofmt to make it pretty (example of what I would type, click Format).
All standard library code which I have read so far was of extremely high quality.
One example is the
image/jpeg package: I didn’t know how JPEG worked at the time, but it was easy to pick up by switching between the Wikipedia JPEG article and the
image/jpeg code. If the package had a few more comments, I would qualify it as a teaching implementation.
I have come to agree with many opinions the Go community holds, such as:
- Variable names should be short by default, and become more descriptive the further from its declaration a name is used.
- Keep the dependency tree small (to a reasonable degree): a little copying is better than a little dependency
- There is a cost to introducing an abstraction layer. Go code is usually rather clear, at the cost of being a bit repetitive at times.
- See CodeReviewComments and Go Proverbs for more.
Few keywords and abstraction layers
The Go specification lists only 25 keywords, which I can easily keep in my head.
In my experience, the small number of abstraction layers and concepts makes the language easy to pick up and quickly feel comfortable in.
While we’re talking about it: I was surprised about how readable the Go specification is. It really seems to target programmers (rather than standards committees?).
Quick feedback / low latency
I love quick feedback: I appreciate websites which load quickly, I prefer fluent User Interfaces which don’t lag, and I will choose a quick tool over a more powerful tool any day. The findings of large web properties confirm that this behavior is shared by many.
The authors of the Go compiler respect my desire for low latency: compilation speed matters to them, and new optimizations are carefully weighed against whether they will slow down compilation.
A friend of mine had not used Go before. After installing the RobustIRC bridge using
go get, they concluded that Go must be an interpreted language and I had to correct them: no, the Go compiler just is that fast.
Most Go tools are no exception, e.g.
goimports are blazingly fast.
Maximum resource usage
For batch applications (as opposed to interactive applications), utilizing the available resources to their fullest is usually more important than low latency.
It is delightfully easy to profile and change a Go program to utilize all available IOPS, network bandwidth or compute. As an example, I wrote about filling a 1 Gbps link, and optimized debiman to utilize all available resources, reducing its runtime by hours.
3. Rich standard library
The Go standard library provides means to effectively use common communications protocols and data storage formats/mechanisms, such as TCP/IP, HTTP, JPEG, SQL, …
Go’s standard library is the best one I have ever seen. I perceive it as well-organized, clean, small, yet comprehensive: I often find it possible to write reasonably sized programs with just the standard library, plus one or two external packages.
Domain-specific data types and algorithms are (in general) not included and live outside the standard library, e.g.
golang.org/x namespace also serves as a staging area for new code before it enters the standard library: the Go 1 compatibility guarantee precludes any breaking changes, even if they are clearly worthwhile. A prominent example is
golang.org/x/crypto/ssh, which had to break existing code to establish a more secure default.
To download, compile, install and update Go packages, I use the
go get tool.
Whenever a program uses more resources than expected, I fire up
pprof. See this golang.org blog post about
pprof for an introduction, or my blog post about optimizing Debian Code Search. After importing the
net/http/pprof package, you can profile your server while it’s running, without recompilation or restarting.
Cross-compilation is as easy as setting the
GOARCH environment variable, e.g.
GOARCH=arm64 for targeting the Raspberry Pi 3. Notably, tools just work cross-platform, too! For example, I can profile gokrazy from my amd64 computer:
go tool pprof ~/go/bin/linux_arm64/dhcp http://gokrazy:3112/debug/pprof/heap.
Note that these are standard tools coming with the language. Coming from C, each of the above would be a significant feat to accomplish. In Go, we take them for granted.
Hopefully I was able to convey why I’m happy working with Go.
Of course, no programming tool is entirely free of problems. Given that this article explains why Go is my favorite programming language, it focuses on the positives. I will mention a few issues in passing, though:
- If you use Go packages which don’t offer a stable API, you might want to use a specific, known-working version. Your best bet is the dep tool, which is not part of the language at the time of writing.
- Idiomatic Go code does not necessarily translate to the highest performance machine code, and the runtime comes at a (small) cost. In the rare cases where I found performance lacking, I successfully resorted to cgo or assembler. If your domain is hard-realtime applications or otherwise extremely performance-critical code, your mileage may vary, though.
- I wrote that the Go standard library is the best I have ever seen, but that doesn’t mean it doesn’t have any problems. One example is complicated handling of comments when modifying Go code programmatically via one of the standard library’s oldest packages,