At my job, I’ve been using a lot of Go lately, and it’s a bit of a mixed bag. Here are my thoughts so far.
Most languages have either a rich type system or a weak type system. Java attempts to have a rich type system: it’s on the strict side, and it gives you generics and type constraints to deal with it. It also lets you cast things between unrelated interfaces if you think you know better than the compiler. Haskell has a much more advanced type system where you don’t need to specify types on pretty much anything as long as you use types consistently. C++ gives you templates, and template instantiation conveniently uses duck typing.
On the other end of the spectrum, we have dynamic languages that don’t worry about types at all. Lua, Python, Ruby — they have no facility for specifying what type a value must be. And in the middle, Dart has optional typing — you can specify all the type information you want or none at all.
Go does not have any type system advancements like in C++ and Java. It has no facility in its type system for coding without knowing in advance what types you are working with. And there’s no way to escape its strict type system.
Today I was trying to write a series of protocol buffers to a set of files. Each file had a different type of protobuf, and I had a list of protobufs that had to go in each file. In Java, that would have been easy — I have a method that takes a
List<? extends Message> and writes each element to a file. How do you do this in Go? Well, the most straightforward way is to copy your array of *foo.MyProtoMessage into an array of proto.Message and — dear god, I just used O(N) extra memory and time to work around a type system, what am I doing with my life? And you investigate a method to method object refactoring just so you can pull a bloody for loop out of a method that can’t handle two types that it should be able to treat identically, and finally, just before you cry yourself to sleep, you remember that you can write a custom iterator and pass in an iterator factory and a length.
2 January 2006
Perhaps the world would have been a better place if we’d originally standardized on date formatting based on a prototypical date. Regardless, that’s not what happened forty years ago. We’ve been using strftime syntax for bloody ages. Diverging from that isn’t doing anyone any favors. The choice of ordering depends on a non-standard time format; they’d have been better off using 2001-02-03T16:05:06-0700.
All warnings are errors
In Go, it is an error, not a warning, to have an unused variable or import. I wanted to comment out one line of code today as a quick test. In order to get that to compile, I had to remove two imports and comment out four other lines of code scattered around the function. Warnings are helpful; making them errors is hostile.
Errors as explicit return values
Go uses multiple return values for error propagation. C does the same, usually. With C, it’s not so bad — you pass in a pointer to the function, and it puts its output in that pointer. If you encounter an error, you return the error value and you’re done.
In Go, you return a default value plus an error value, or the actual return value plus a nil error value. This gets annoying fast, especially since there doesn’t seem to be a convenient way to specify the default value for a type. The problem is that errors aren’t a first-class entity in Go, with proper support. You pretty much never want to return an error plus another value; you want to return an error xor a valid value. But that isn’t a general use case; it’s a use case that’s rather specific to errors. So the designers of Go chose instead to implement a general thing that makes the special case of errors awkward to use.
All in all, Go is an okay language, but it grates on me. It has some nice syntactic ideas, but overall it’s not fun to work with.