My thoughts on Go 🐹
Last week, I started a small hobby programming project. It’s called Laebel and is a server that runs within a Docker Compose project, serving an automatically generated README-style documentation site.
From a development perspective, the basic requirements for the project included accessing the Docker API and being able to generate and serve HTML pages. Not very challenging criteria to be honest, but I felt that my normal go-to language Kotlin and the JVM that comes with it was a bit overkill. I just wanted something small and easy.
Since you can build a HTTP server in virtually any language, I focused on which languages have good Docker API libraries. After some very quick research, I concluded that Go probably tops the list. After all, Docker itself is written in Go. I also happened to be in the mood for experimenting. Learning Go felt like an interesting challenge, and their Gopher mascot is cute.1
First impressions #
The Go programming language is new to me2, so I got to see it with fresh eyes.
-
The first thing that stood out to me is that Go feels very simple. I mean that both in the sense that it is not a complex programming language, and that it feels easy to get started. And I consider both of those to be positives. I’m sure I’ve missed a lot of nuances, but I felt I could intuitively understand most Go code I looked at. After a brief learning curve, most of my coding attempts were successful on the first try.
-
Another immediate impression was that the code produced by Go is fast and lean. It compiles into a single self-contained executable. Compilation takes just a second or so, and the resulting application starts instantly. That is refreshing, since Gradle wouldn’t even had started to build a Kotlin project in that time. 😉
-
A third thing that pretty much jumps at you when starting to write Go code (at least with my background) is the explicit error handling. If a function can produce an error, an
error
will be returned by the function, and you are expected to deal with it immediately. No throwing or catching of exceptions. That leads to a lot of code like the example below. It does become somewhat tedious. In some functions, it feels like every statement takes four lines – one for the function you wanted to call, and three for the error handling.containerID, err := GetContainerID() if err != nil { log.Fatal("Could not determine current container ID:", err) } // Now we can use containerID
-
Finally, it felt like there was always an easily accessible library for what I wanted to do: string templating to generate HTML, a HTML server with basic routing to host it, as well as logging and testing libraries. The only thing I had to reach outside the standard library for was the Docker API, but that felt easy too.
Far from functional #
After a few hours, the basics were becoming familiar. That provided opportunity for some deeper reflection.
Over the past decade, much of my learning has led me toward functional programming.3 That feels quite far away when working with Go. Sure, there are first-class functions, but that is about it. Go emphasizes simplicity, explicitness, and performance. It encourages an imperative style and explicit control flow. No map
or filter
functions—at least, not in the standard library. If you want to iterate, just use a for
loop the way God intended.4
It does feel quite odd having to do things that I’ve spent years trying to get away from. Things like reassigning variables, initializing data structures in an “empty” state, and accidentally accessing a variable that happens to be nil
rather than what I hoped for.
I’m so done with “null pointer exceptions” #
After more than a decade in Java, moving to Kotlin and its null safety felt great. TypeScript is also pretty good in this regard. Working with Go feels like a big step back.
Go allows you to dereference a pointer implicitly, for example when accessing a field on a struct held through a pointer. If that pointer turns out to be nil
, you’ll get a runtime surprise.
var person *Person // person is a pointer to a Person struct
person = &Person{ // & means we take the address of the struct we create
Name: "Henrik",
Age: 42,
}
//person = nil
name := person.Name // Let's hope person is not nil!
If you uncomment the nil
line, the program will still compile but panic at runtime.
panic: runtime error: invalid memory address or nil pointer dereference
I find this design decision a bit surprising, since Go clearly favors explicit error handling when it comes to other runtime errors. But since Go is built for systems programming, I guess it comes with the territory.
Other odds and ends #
A few more things that I noticed while learning Go.
- I was surprised by Go’s use of casing to control visibility. Declarations such as functions and types that start with a capital first letter (
PascalCase
) are exported to other modules, while those with a lowercase first letter (camelCase
) are internal. - I’ve had a lot of help from ChatGPT. Not necessarily to produce Go code for me, but to give me pointers to where to look, generate examples to use as a starting point, and to explain conventions and idioms.5
- I’ve learned that Go is structurally typed. After working with TypeScript for a while, this is starting to feel familiar. I sometimes even miss it when going back to the nominally typed Kotlin.
- Working with new stuff gives me a nice feeling that I can improve a lot quickly. After writing most of this post, I stumbled upon Some Go web dev notes by Julia Evans. It is a similar article as this one, but from someone who knows Go better. Just reading it I learned two new things that I immediately could apply to my project.
In the end, I’m pleasantly surprised in how quickly I could get the idea I had in my mind into working Go code. I expect to continue using Go for a while as I develop this project. However, I don’t think it will become my go-to (pun intended) language for other projects. I would miss my functional foundations3 and null safety a bit too much.6
What about you, what are your thoughts on Go?
-
I’m sorry about the hamster emoji in the title, but there does not seem to be a gopher emoji to match the mascot of Go. At the same time I’m happy to find a use for the hamster emoji, especially since I try to assign a unique emoji to each of my blog posts. 😂 ↩︎
-
I did one job interview test in Go once, so I had some vague feeling of where I was heading. ↩︎
-
In Functional foundations, I try to capture the parts of functional programming that I feel programmers of all backgrounds will benefit from. ↩︎ ↩︎
-
Go intentionally chooses rejects the declarative style of iteration I describe in There i no loop. It prefers an explicit and imperative style. ↩︎
-
This matches my previous experience, where I’ve found that AI is great for research but not a replacement for thinking. ↩︎
-
Clearly other people have had the same feeling, seeing the development of the V programming language. It is like Go but with all the things I miss added on top, like null safety, immutability, algebraic data types, and much more. ↩︎