A C++ Programmer tries Go

Andrew Stephens, Wednesday the 28th of December, 2016

I consider myself a C++ guy for the most part but I like to dabble in other languagesThe software that runs this blog is written in Python, for instance. So far I have avoided Go as a language for the same reason I have avoided CrossFit - people who use it will seem incapable of shutting up about how great it is like some sort of reverse fight club.

But I find myself casting around for a language that is good at developing web-based services and Go seems like a natural fit. I've spent a few hours trying out a few things and in general I like what I see.

Environment

I don't think I have a good handle on exactly how the build environment works, but the basics are pretty simple. A simple directory structure is enforced and third-party packages just slot into place.

The recent trend of having library managers built in always makes me nervous, I don't like the idea of building a project from scratch 3 years from now only to find that half the libraries no longer exist, but Go's approach seems sensible and the packages I have seen have been professionally put together.

Languages live and die on their standard libraries, and Go has an excellent set of packages with particular emphasis on web technologiesJson, Http, there is even html templating and a pretty good image library. I haven't even looked at the massive array of third party packages yet, everything I need has been in the base set.

One thing that sort of surprised me is that Go compiles (very quickly) to native executables. This is great for performance but may make it difficult to deploy applications unless there is some way of cross compiling.

Syntax

Go's syntax has some odd choices. There is a strange rule about semicolons - they are required but implied, so you usually don't type them and the compiler will put them in as it compiles. But this means that you cannot put the opening brace of a control block on its own line.

if a < 7 {     // this is OK
  DoSomething()
} 
else           // this will not compile, { must go on the same line
{

}

This is fine, just a weird thing to enforce.

There are two ways to declare variables.

var thisIsAnInt int
thisIsAlsoAnInt := 15

The := operator declares a variable just like the var keyword with the type implied by the right hand side. After a variable is declared it can be assigned with = as normal. I like this much better that the implicit variable creation in languages like Javascript.

There is no while loop, the for keyword has been adjusted to cover this base.

for a := nextFile(); a != nil; {
  fmt.Println(a)
}

Defining types is pretty straight forward, there is not true inheritance but interfaces are supported (duck-typing-style).

I am not crazy about determining whether a symbol is public or private to a package by whether the first letter is upper or lower case but I guess it saves a keyword.

On the whole, Go's syntax seems pretty straightforward, I am certain I can get used to it.

Goroutines and Channels

I haven't had much of a chance to play these yet but they seem like the jewels in the Go crown. Goroutines are lightweight concurrent functions, the runtime manages the OS threads for you. All you do is

go SomeFunctionIWantToRunConcurrently()

and things happen magically. It really is a nice programming model and it gets even better. Channels are typed in-process pipes, pop something on a channel and some other part of your program can read it in a thread-safe manner. This leads naturally to implementing the actor pattern and high-performance servers.

I am not thrilled about the syntax though

c <- 50  // push the value 50 onto the channel c
result := <-c  // receive the value (blocks if no value is available)

It is possible (and easy) to wait on channels or read from multiple channels in a non-blocking fashion. It really is a well thought out system and I have plans for it.

Handling Errors

Go has multiple return values, and almost all APIs return a second, optional error value

result, err := someOperation(); 
if err != nil {
  fmt.Println("An error occured!")
}

Go types do not have destructors but cleanup is simplified by defer'ing code until a function ends (however that happens).

f, err := os.Open(filename)
defer f.Close()

Go does not have exceptions, except that is sort-of does in the form of panic. When a function panics, it ends immediately (running all the defers) and then the calling function also panics, etc. One of the defer'ed blocks up the stack can recover and grab the argument passed to the original panic.

This sounds a lot like an exception to me but function based rather than block based. Exceptions are a contentious issue and I can see how Go's scheme might enforce a little discipline on the sometimes chaotic exceptions of languages like C++.

On the other hand, I really like the RAII-style bullet-proof coding that you can achieve with C++ style destructors. defer seems like an easy thing to forget.

Conclusions

I've really only begun to scratch the surface of Go and I'm sure I will run into more things that will take time to internalize. The thing that excites me the most is the goroutines, writing asynchronous services will be simple and easy. Everything else I can live with.