Article summary
I recently had the opportunity to learn the Go programming language as part of a project. Go is famously touted as easy to learn, but I was skeptical. After working with it for a few months, I’ve had time to form some initial impressions.
Syntax
The first thing that struck me about Go is how C-like it is. Sure, lots of languages borrow syntax from C. But Go programming language feels like what would happen if C were a scripting language.
But everything in Golang is backward compared to C and other C-like languages. Instead of typing an array as int[]
, it is []int
in Go. It was awkward at first, but there’s a good reason for it. In C, declarations read in a spiral! But in Go, they read from left to right. I haven’t written a lot of actual C code, but I have had similar frustration with block syntax in Objective-C. So, I can appreciate that Go went its own way here.
Another worthwhile trade-off in Go is the garbage collector. The presence of a garbage collector is nothing to write home about. But without garbage collection, Go would be much less attractive. You must still be careful to release resources when you’re done with them, but the freedom from manual memory management avoids contributing extra noise to your code.
What does cause a lot of noise is error handling. Go doesn’t have exceptions. It has panics, which operate a lot like exceptions, but panics are reserved for truly world-ending situations. Most of the time what happens is that when you call a function, it will return either an error
or nil
. Your code quickly fills up with if err != nil
checks, and you might wish for exceptions, or least some kind of Result
-based pipeline like those seen in many functional languages.
Go also doesn’t have classes. This is probably for the best since inheritance is the worst part of most class hierarchies. What you get instead is the humble struct
. A struct is simply a bag of properties. Structs can have “methods” but these amount to nothing more than syntactically-sugared functions that accept the struct as the first parameter.
Types
Go’s type system revolves around interfaces. An interface works how you’d expect in just about any language: a list of methods. But nobody has to explicitly implement
an interface. A struct that implements all the methods described by an interface inherently implements that interface.
And although it’s a fairly recent development, Go’s type system supports generics. I can just imagine that the language designers were dragging their feet on this one, but generics provide building blocks for static type safety.
Go largely dispenses with null by giving every type a “zero” value. For integers, this is 0, for strings it is “”, etc. Pointers and some other built-in types can still be nil
, but Go doesn’t provide any syntax for dealing with it. No null-coalescing operator, optional chaining, etc. If you want these things you can try to bolt them on using generics.
Speaking of missing syntax, Go doesn’t even have a ternary/conditional operator. What gives? You end up writing clumsy statements like this:
result := "cold"
if temperature >= threshold {
result = "hot"
}
But although the drab syntax makes Go feel like it takes longer to write, it runs pretty fast. The compiler is fast, and if you’re coming from the node/javascript world then a compiled Go binary will be refreshingly performant. Being able to easily take advantage of multiple cores might just make up for all the aforementioned tedium.
Goroutines
Of course, Go wouldn’t be Go without goroutines, Go’s very own brand of concurrency. Goroutines can be thought of a lot like threads, but internally they are even lighter than that. Goroutines are also not async
/await
, which is mostly used to avoid blocking on a single thread. Instead, goroutines are like one-off operations that ultimately get scheduled to run on a thread.
Standard threading usually comes with its own tedium like managing thread pools, synchronizing critical sections, and so on. But Go attempts to remove much of this overheard by setting up goroutines to communicate using channels. A channel is basically a thread-safe pipe/FIFO. This is great in theory, but you’ll often end up resorting to manual locking to keep parallel goroutines from stepping on each other or causing race conditions.
Dependable Over Flashy
The whole time I’ve been using the Go programming language, I keep coming back to the question: what is Go’s killer feature? What is the reason that I would choose Go over any other language? My initial thought was that the killer feature is goroutines. The name “go” is right there, and no other language has goroutines, so that must be it, right?
Goroutines are neat, but ultimately I don’t think they’re compelling enough to warrant using Go over any other programming language with threads. I think the reason to choose Go is that it’s a straight-jacket. Its aggressively simple syntax prevents you from doing anything too clever. Go ends up adhering more to the Zen of Python than Python itself. I’d have to agree that it is easy to learn, especially if you’ve already got a few other languages under your belt. Go is not very flashy, but it is dependable and efficient, and maybe that’s exactly what you need.