Golang - First Impressions

2021-11-14

Calling it “first impressions” may not be entirely accurate on my part, after having invested well over 60 hours in learning the Go language, because at this point I already feel a little bit familiar with it. It is true what they say. The Go language is really quite easy to pick up, especially when you have some experience with the C family of languages. Anyway, it’s still “first impressions” for me , as I’ve just learned the key concepts of Go and I am neither familiar with the entire standard library nor the entire Go ecosystem. Quite possibly, I will one day look at the code I am currently writing and consider it to be rather clunky. It has happened before with other languages.

It was March when I formed the idea of learning Go and it was inspired by projects in the DevOps and distributed systems programming space, such as Kubernetes, Docker, Consul, TiDB, InfluxDB and CockroachDB. It took me 7 months to eventually get to it, but that’s life. While I started my career with universal, statically typed, compiled languages, I’ve spent the last decade mainly working with dynamically typed interpreted languages. For me, Go offers a nice modern alternative to C/C++, while it feels a bit like good old days of C. Minus the pointer arithmetic, of course, but who needs pointer arithmetics in 2021. So, here is my five minutes Go rap.

Photo by Ivan Samkov

Let me tell you that programming in Go is fun. There are three things that contribute to the satisfying experience: a flat learning curve, great documentation, and an excellent tool set. The Go compiler is really fast and the “go” command quickly becomes your best friend, as it offers everything from compilation, dependency management, code formatting and refactoring to testing and profiling. A Swiss army knife for Go, so to speak, just add your favourite code editor and you’re all set. So, here is what I like about Go:

  • Safe memory management. You can plan and optimise memory allocation, but you don’t have to. Thanks to garbage collection, you also don’t have to worry about de-allocation. In 90% of all cases this makes life easier. Exceptions: games and real-time apps.
  • Straightforward concurrency and parallelism with lightweight Go routines. Channels and mutexes for IPC. All of that baked into the language thus recognising the fact that concurrent and parallel applications are must-haves in the era of multi-core processors.
  • Cross-compilation to monolithic binaries. That is really applied KISS. I can compile for my Intel machines or for my Raspberry PIs simply by flicking an env switch. No runtime, no libraries, no packages. Makes deployments super-simple. Did I mention the fast compiler?
  • The absence of inheritance. Really, who needs inheritance? It’s an academic concept that -in my experience- rarely delivers and often makes code harder to understand. It can always be replaced with composition. If you’re coming from Java or C# this may be a bitter pill to swallow, of course.
  • The absence of classes. In Go, objects are structs with added behaviour. While there are no formal class declarations, you can define types and interfaces and create instances of them. It’s really OOP in its simplest form. Applied KISS once more.
  • The presence of function values, function parameters and closures. Opens the door to functional programming. Though the standard library doesn’t seem to follow suit in a functional direction.
  • Implicit interfaces. This allows you to define interfaces for existing third-party code according to the needs of your application. It possibly defies the “design by contract” idea, but it renders the greatest amount of flexibility.
  • Defer statement. Clever idea that improves code readability.
  • Tuple return values.
  • Variadic arguments and destructuring.
  • Type inference. In 2021, I really wouldn’t look at any language that doesn’t have it.
  • “Gofmt” code auto-formatting to canonical style (like it or not) which puts an end to all time-wasting code style debates.

Everything has two sides, of course, and there are a few Golang characteristics that appear to be in the twilight zone, at least from my perspective.

  • Error handling in Go lacks exceptions and is treated idiomatically in a C-ish style using error return values. This is not just extremely tedious, but also throws a wrench into API chaining.
  • Lack of generics. Well, this is a big one. Not having generics means that you have to duplicate code for different types. There is a way around this by using reflection in Go, but it is clunky. The Go community has addressed this issue and decided to implement generics in Go version 1.18, which is due to be released next month. I am excited.
  • The empty interface. A slightly mysterious Go concept enabling the passing around of “any” type.
  • The lack of higher order functions such as map(), filter(), reduce().
  • The lack of a ternary operator. You really do have to write if…else (with brackets!) every time.

And that’s about it. The positives clearly outweigh the negatives. Go was designed with simplicity in mind, and it is admirable how well this design goal was achieved. I think it’s a far better choice for system programming than C or C++. Writing Go code is straightforward and productive. I think it could be characterised as a true workhorse. One thing I noticed though is that boilerplate code quickly accumulates when writing Go programs. It may be due to the C-style error handling, the lack of generics, and perhaps also due to the restricted set of language constructs.

Of course, there isn’t any program you cannot write with a for loop and an if or switch statement, but the result may not always be the most elegant. In spite of this, I am sufficiently impressed with Go to consider a more ambitious project. I will surely keep a close eye on the future development of this likeable language.