Here's why I love Go

Many years ago, I've been waiting for a language like Go. Back then, I was primarily working with C (for microcontrollers), and C++/Java/Python/etc (for bigger machines). These are great tools, but still I was longing for compiled, statically typed, memory-safe (and garbage collected) language, which compiles for the target platform machine code, and has a big enough community. I looked at the Zimbu programming language; the goals of which match mine quite well: It has to be as fast as possible, so interpreted languages are out.

You don't want to micro manage memory, so C is out.

You don't want to require programmers to have a degree, so C++ is out.

You want fast startup and not depend on a big runtime, so Java is out.

It has to run on most systems, anything with a C compiler, so D is out. But Zimbu is a one-man show, so obviously it didn't match the “big enough community” requirement. And actually I find some parts of Zimbu syntax totally awful: FUNC Main() int IO.print("Hello, World!") RETURN 0 } For example, the lack of opening brackets distracts a lot. Bram Moolenaar (author of Zimbu) is known for another language with awful syntax however: Vimscript, so nothing to be surprised about. ;) And a few years ago, I discovered Go.

Focused on practical solutions

Go is very practical. Its design is focused on practical solutions rather than being driven by theoretical perfectionism. I often hear people complaining about it not having a feature X (indeed, Go lacks lots of features often present in other languages), but actually I do like this minimalism. For example, consider error handling. Go doesn't have exceptions, so the common idiom is for the function to return multiple values, the last one being an error . And, of course, the caller should check that error, and react appropriately. This is a common idiom: value , err := DoSomething () if err != nil { return errors . Trace ( err ) } On the cost of some repetition here and there, it makes programs look very explicit, which helps readers to understand the code better, instead of wondering: can the exception be thrown here or not? (However, it's sad that very few people use this errors.Trace(err) thing, more about it here)

Concurrency

In my opinion, the way concurrency is designed and implemented in Go is outstanding. Actually, Go supports two styles of concurrent programming: the traditional shared memory multithreading, and the encouraged communicating sequential processes (CSP). With shared memory multithreading, we use mutexes, conditional variables and other concurrency primitives often found in other languages. With CSP, however, we have goroutines and channels. You can think of goroutines as lightweight threads. Semantically they are threads, but extremely cheap ones. It's not uncommon to have thousands of goroutines running concurrently. Channels are means by which goroutines communicate with each other. First of all, let me quote a paragraph from the “Share Memory By Communicating” article:



Do not communicate by sharing memory; instead, share memory by communicating. Go's concurrency primitives - goroutines and channels - provide an elegant and distinct means of structuring concurrent software. (These concepts have an interesting history that begins with C. A. R. Hoare's Communicating Sequential Processes .) Instead of explicitly using locks to mediate access to shared data, Go encourages the use of channels to pass references to data between goroutines. This approach ensures that only one goroutine has access to the data at a given time. The concept is summarized in the document Effective Go (a must-read for any Go programmer):Do not communicate by sharing memory; instead, share memory by communicating. For me personally, the concept of goroutines which communicate with each other by means of channels, translates very well to the concept I'm using in embedded real-time kernels (e.g. my beloved TNeo), where threads communicate with each other by means of message queues. So they were quite familiar to me from the beginning, but the fact that goroutines are very cheap opens additional possibilites, of course. I do use mutexes and conditional variables occasionally, but still prefer goroutines and channels, as it leads to better designed programs in Go. This topic is large enough, so I'm not going to repeat all of it here; refer to the linked articles for details.

Go is a better* C

*Better for most applications. I was hoping it's obvious, and I myself keep using C for embedded stuff I work on, but some people on reddit keep arguing that Go might also be “a worse C”, so I had to add this disclaimer. I'm not the first person who draws a parallel between Go and C, still it's a good point to emphasize. I wrote a lot of code in C, and I try hard to make that code good. Perhaps surprisingly, overall I do like C, except for a few things which I don't like: Having to micro manage memory all the time;

Handling errors properly is a pain;

Writing concurrent code is a pain;

Writing cross-platform code is a pain;

Standard library is in many aspects awful. For example, in 2017, asprintf() and timegm() are still not standardized; I just can't understand it. Go solves all these problems for me. So, in the end, we have C without its flaws, and it sounds like a really awesome language! Yes, I think it is awesome indeed.

Compilation is fast, crosscompilation is painless

First of all, of course, the compiler. To build a go project, there is a command go build , and it works really fast. Cross-compilation is surprisingly painless. I can produce a Windows binary from my Linux machine with just one command, like: $ GOOS=windows GOARCH=386 go build

Open source

The fact that the language and its standard library are open source makes it much easier and fun to learn. Not only we can use this to figure how something works; we can learn how experts write really good Go.

Race detection

Go runtime can detect race conditions; this feature provides tremendous help given how difficult it could be at times to find such a bug in a complex system. To build the project with that feature enabled, build it with the --race flag.

Aux

Other than that, we have tools for auto-importing packages, renaming, code formatting, etc. The code formatting part might sound minor, but still, did you ever have to waste time arguing about the style? I did, and I don't think it's a good kind of activity. The gofmt tool doesn't even have any options to adjust the output! It just formats your code, it uses tabs for indentation and spaces for alignment, the output is certainly good enough, so we can proceed to more important things rather than arguing about the style.

Easy IDE-free development

Another point which absolutely like is that all standard tools only have command-line interface, so it's easy to integrate into any kind of workflow, instead of having to install and adapt to a new gigantic IDE, etc (that's a sensitive point for me, which I explained in a separate article). I personally just use the vim-go plugin for Vim, but you can choose whatever editor you want.

Go makes me love web development

What I totally dislike about web development nowadays is that using dynamic languages for that (like Ruby, Python, JavaScript, PHP, etc) is a norm, even for large and complex web applications. And in general, web apps tend to be complex. I'm not saying that the languages are bad (well, PHP though… is a different story), however I personally use Python or Ruby only for short(-ish) scripts and things like that, when I want to get something done quickly. But when it comes to complex apps, static type system provides tremendous help. For example, when I try to compile some freshly-written (or refactored) module for the first time, I'd be very surprised if the compiler does not complain; most of the time there are some issues which I overlooked. So, if I didn't have the type system, I'd have to deal with these issues at runtime. This static analysis helps a lot to catch bugs early, so why not use that help? Not only static types help compiler to catch bugs early, they also help reader to understand and navigate the code better. This is executable documentation, if you like. People seem to agree that writing tests is a good thing, because it leads to better software. I really hope that sooner or later people will also agree that using statically typed languages also leads to better software (we even have TypeScript already!), and when it happens, Go looks like a very good candidate. Of course, Go is suitable for many things other than the web; however it has everything one would need for backend web development: decent web server with http/2 support, sane parser/serializer for json, yaml, xml and such, etc. You can handle xmls and not go crazy! But this topic deserves its own article (or a few of them).

What I dislike about Go

Not much. Off the top of my head, just a few things:

The flag package

First of all, not exactly about the Go itself, but its standard library: I said above that I do like the minimalism of Go, but some parts of it are below the threshold even for me. A shining example is the standard flag package; in my opinion, this is where people simplify too much. This package is just plain dumb. It doesn't distinguish between single-dash and double-dash flags (so --foo and -foo are equivalent), there's no way to make a flag required, no way to know whether the flag was given by the user or the default value was assumed implicitly, etc etc. I'd like flag parsing package to be as fashionable as Python's argparse, but, alas. Luckily, there are some third-party alternatives.

No generics

I don't have a pressing need for generics, but sometimes I do want to have them. FAQ says that they may be added to the language at some point, so I'm full of hope.

No way to enforce initialization of some struct's field

Consider a simple struct: type Foo struct { a int b int } We could initialize an instance of it like that: f := Foo { a : 1 , b : 2 } This is good and self-documented, but the problem is that when we add more fields to the struct, that initializer code will continue to compile with zero values being set for the newly added fields, and this is not necessary what I want. I'd like to be able to annotate some field to require explicit initialization of it. That's not the end of the world, but still something to complain about, as I keep thinking of it rather often. Alternatively we could use Foo{1, 2} : in this case, adding new fields would force us to update initializer as well, but it's not self-documenting and error-prone in other aspects, so I tend to avoid that form.

GOPATH

For the Go toolchain to work, source code must be located on a certain path, which is determined by the environment variable GOPATH . This does cause inconveniences, which are unnecessary in my opinion. Luckily, this part is (hopefully) going to be solved: Another future usability improvement that dep might enable, is allowing Go to work from any directory (not just a workspace in the GOPATH), so that people can use the directory structures and workflows they're used to using with other languages.

Conclusion