Author: Nick Cameron (Database Engineer at PingCAP)
Editor: Caitin Chen
I've been using Go for the past few weeks. It's my first time using Go for a large (-ish), serious project. I've previously looked at Go a lot and played with examples and toy programs when researching features for Rust. Real programming is a very different experience.
I thought it would be interesting to write about my impressions. I'll try and avoid too much comparison with Rust, but since that is the language I'm coming from, there will be some. I should declare in advance a strong bias for Rust, but I'll do my best to be objective.
Programming with Go is nice. It had everything I wanted in the libraries and there were not too many rough edges. Learning it was a smooth experience, and it is a well-designed and practical language. Some examples: once you know the syntax, many idioms from other languages carry over to Go. Once you've learned some Go, it is relatively easy to predict other features. With some knowledge of other languages, I was able to read Go code and understand it without too much googling.
It is a lot less frustrating and a lot more productive than using C/C++, Java, Python, etc. It did feel, however, like part of that generation of languages. It has learnt some lessons from them, and I think it is probably the best language of that generation; but it is definitely part of that generation. It is an incremental improvement, rather than doing something radically different (to be clear, this is not a value judgement, incremental is often good in software engineering). A good example of this is nil
: languages like Rust and Swift have removed the concept of null
and eliminated a whole class of errors. Go makes it a bit less dangerous: no null values, distinguishes between nil
and 0
. But the core idea is still there, and so is the common runtime error of dereferencing a null pointer.
Go is incredibly easy to learn. I know this is an often-touted benefit, but I was really surprised how quickly I was able to be productive. Thanks to the language, docs, and tools, I was writing ‘interesting’, commit-able code after literally two days.
A few factors contributing to learnability are:
Go code becomes very repetitive very quickly. It is missing any mechanism like macros or generics for reducing repetition (interfaces are nice for abstraction, but don't work so well to reduce code duplication). I often end up with lots of functions, identical except for types.
Error handling also causes repetition. Many functions have more if err != nil { return err }
boilerplate than interesting code.
Using generics or macros to reduce boilerplate is sometimes criticised for making code easy to write at the expense of making it harder to read. But I found the opposite with Go. It is quick and easy to copy and paste code, but reading Go code is frustrating because you have to ignore so much of it or search for subtle differences.
if ...; ... { }
syntax. Being able to restrict the scope of variables to the body of if
statements is nice. This has a similar effect to if let
in Swift and Rust, but is more general purpose (Go does not have pattern matching like Swift and Rust, so it cannot use if let
.Go
tool is nice - having everything in one place rather than requiring multiple tools on the command line.In no particular order.
nil
slices - nil
, a nil
slice, and an empty slice are all different. I'm pretty sure you only need two of those things, not three.switch
may be non-exhaustive.for ... range
returns a pair of index/value. Getting just the index is easy (just ignore the value) but getting just the value requires being explicit. This seems back-to-front to me since I need the value and not the index in most cases.return
statement.type
and struct
).I've already mentioned the lack of generics and macros above.
As a language designer and a programmer, probably the thing that most surprised me about Go is the frequent inconsistency between what is built-in and what is available to users. It has been a goal of many languages to eliminate as much magic as possible, and make built-in features available to users. Operator overloading is a simple, but controversial, example. Go has a lot of magic! And you very easily run into the wall of not being able to do things that built-in stuff can.
Some things that stood out to me:
for ... range
statement for iterating over arrays and slices, but you can't iterate over other collections because there is no concept of iterators.len
and append
, are global, but there is no way to make your own functions global. Those global functions only work with built-in types. They can also be generic, even though Go has ‘no generics’!==
because it means you can't use custom types as keys in a map unless they are comparable. That property is derived from the structure of a type and can't be overridden by the programmer.Go is a simple, small, and enjoyable language. It has some odd corners, but is mostly well-designed. It is incredibly fast to learn, and avoids any features which are not well-known in other languages.
Go is a very different language to Rust. Although both can vaguely be described as systems languages or ‘replacements’ for C, they have different goals and applications, styles of language design, and priorities. Garbage collection is a really huge differentiator. Having GC in Go makes the language much simpler and smaller, and easy to reason about. Not having GC in Rust makes it really fast (especially if you need guaranteed latency, not just high throughput), and enables features and programming patterns which are not possible in Go (or at least not without sacrificing performance).
Go is a compiled language with a well-implemented runtime. It is fast. Rust is also compiled, but has a much smaller runtime. It is really fast. Assuming no other constraints, I think the choice between using Go and Rust is a trade-off between a much shorter learning curve and simpler programs (which means faster development), versus Rust being really, really fast, and having a more expressive type system (which makes your programs safer and means faster debugging and error hunting).