5 Weeks of Go

I promised a few people that I’d write up my impressions of Go after spending 5 or so weeks learning it while developing Amberfell. I’m not an expert in computer language design nor do I have extensive experience in obscure languages. I pick my language based on how productive I and others can be with it not on how pure it is.

In my opinion the Go designers have done an excellent job of blending the flexibility and convenience of a scripting language with the performance and safety of a strongly typed compiled language. Coupled with its special support for concurrency and excellent standard library this makes Go a great language to work with. The amazing speed of the compiler means the development cycle is a fast as a scripting language even though full optimizations are always switched on.

Go recently reached version 1.0 which is intended to remain stable for a number of years to come.

Syntax

Go is a small language in the vein of C, easy to learn the basics in a couple of hours. The language specification is under 23,000 words long and there are only 25 reserved keywords. As an example, there is no separate while or do constructs, they are both collapsed into a simpler for:

for condition {
    function()
}

The language designers have done a good job at removing extraneous cruft and boilerplate. The most obvious is the absence of semicolons. The only time you need to use them is when you want multiple instructions on one line. Otherwise you can simply leave them off the end of lines. It sounds trivial but it really enhances readability and eliminates a range of stupid typo errors you see in other languages. Another welcome cruft-removal is the loss of parantheses around conditions in if statements.

Variable declaration uses the var keyword followed by the name and type of the variable and optionally an initializing value. However, if you have an initializing value then you can simply assign it and the compiler works out the type for you. In the following both foo and bar are of type float64:

var foo float64 = 1.5
bar := 1.5

One thing I thought was odd at first was that the capitalisaton of a function determines its visibility outside of a package. Functions (and variables) that start with a capital letter are visible, those starting with a lower case letter are package local. When I first saw that I was disappointed since it made me think of FORTRAN. However in practice it’s not been a problem at all and is a really simple convention that again removes the cruft of having to mark things as explicitly private or public.

This leads me onto the next point: Go is opinionated about its formatting, for example in the placement of braces in an if/else block. A lot of people will recoil from this, getting precious about where they should put their braces and how many spaces to use as an indent. However, if you’ve programmed in Python then you’ll be used to significant formatting. My advice is simply to let go of your prejudices and Go with the flow (!!). The benefits of consistent formatting really pay off. Enforcing a brace placement style and other conventions means the compiler can be super fast.

It also means that Go can ship with a built-in code prettifier so all the Go code you ever deal with will have a consistent format and layout. Just run go fmt in your package directory. I run it as part of my git commit.

Packaging

Packaging in Go is simple and powerful and borrows heavily from Python but with the unique twist that it's web-aware. Standard packages are imported by name and there's no concept of importing a single name from a package. Names imported from a package are accessed by using the last component of the package name as a prefix (rather than the horrendous full package prefix that you often see in Java and similar languages)
import "math"
import "fmt"
import "image/color"

x := color.RGBA{255, 255, 255, 0}

However you can also import web-accessible packages by specifying a portion of their URL:

import "github.com/banthar/gl"
x := gl.Init()

You can install that package by running

go get github.com/banthar/gl

which tells Go to check out the package from github automatically for you. It will also follow any dependencies it finds and fetch those for you automatically. This means there are no separate configuration files to describe dependencies - it’s right there in the code. In fact the “Go Way” to distribute your packages is by hosting it in a code repo, in the canonical file structure. Go understands Mercurial, Git, Bazaar, Subversion plus a recent patch uses a meta tag in an HTML page to specify a repo to use.

This is great for distributed development and it bakes into the core language some of the learnings from other language ecosystems (gems, maven, pip etc).

Anti Features

The features of Go such as goroutines and garbage collection are pretty well known so I thought I'd focus on what I think are important anti-features, i.e. things that have deliberately not been added.

The first of these is trivial: there are no warnings in the compiler, only errors. Your code is either good to go or it isn’t. Black or white. Warnings are a symptom of an indecisive language and Go is firmly decisive and forces the developer to decide on a solution. Most compiler warnings are bugs waiting to happen.

The second is no dynamic linking (of other Go code, you can link to C libraries). That really bucks the trend of modularity in modern development. However, here’s the point: dependency is a synonym for modularity. If your modules aren’t controlled at runtime then neither are your dependencies, and likely as not, nor are their dependencies. This implies that a well-meaning upgrade of a library or a jar can cause unintended consequences and so requires a large integration testing cost for every upgrade. How many services have you seen that have been running for months or years with their own local copies of libraries so other services on the box can be upgraded without paying the cost of retesting the first service? Static linking means you compile the service and deploy it. End of job.

The third is no exceptions. This is highly controversial in many circles, but I’ve long held the opinion that exceptions are broken by design because they force errors to be handled at points far away from the cause. How many codebases contain the following:

catch (e Exception) { 
    // TODO: figure out what to do here
}

Or you get detailed exceptions thrown that bubble up implementation details to the outside world. For example, you thought you’d encapsulated the fact that your IO was running over a network or a local disk but now you main application loop needs to know what to do when the DNS server goes away.

Go replaces exceptions with return values, but since Go functions can return multiple values these are in addition to the normal return value(s). Go provides the very neat defer keyword that takes the place of final sections, ensuring that code is run no matter what happens in the function. Still not convinced? Read what the architect of ZeroMQ has to say on the subject.

Those first three anti-features are aimed at making Go into a language suitable for building robust, large scale systems. There are no warnings so there are fewer hidden bugs waiting to bite you. Deployment and dependency management is simpler and therefore cheaper. Code is more robust because you’re forced to deal with errors close to where they occur and where you have the most information about the problem. The final anti-feature is a little different and probably even more controversial than lack of exceptions.

Go is not object-oriented. This, in my opinion, is a massive plus for the language. I can hear the die-hards screaming already about encapsulation, inheritance and polymorphism. It turns out that these things are not as important as we once thought. I’ve spent a decade working closely with RDF and structured data in general and I came to the conclusion several years ago that OO was simply the wrong paradigm for the real world. In the real world, things are fuzzy and they spread across multiple conceptual boxes all at once. Is it a circle or an ellipse? Is she an employee or a homeworker? Classifying things into strict type hierarchies, although seductive, is never going to work. In RDF we’re more than happy for a thing to have multiple types (a person, a woman, an employee, a researcher, an adult) with varying properties (name, age, gender, workplace, height) that may or may not apply to any or all of the types.

It’s human instinct to classify things into buckets based on what they look like but that instinct is wrong. Instead we should treat things by how they behave and this is the essence of Go’s approach. Idiomatic Go is oriented around interfaces that specify behaviour. If a type implements the methods in the interface then it can be used wherever the interface is required. However, the smart bit is that unlike Java interfaces, in Go you don’t need to declare which interfaces you implement: the compiler just works it all out for you.

This is duck typing on steroids because it’s strongly typed so it’s all checked at compile time rather than runtime. It’s as powerful as the duck typing in a dynamic language like Python or Ruby but it catches stupid errors such as calling a method with the wrong number of arguments or passing a string when a float is needed.

As an example, in Amberfell I have a TimedObject interface which is very simple:

type TimedObject interface {
	Update(dt float64) bool
}

Anything that has a method with that signature can be used where I need a TimedObject, e.g. my Furnace type:

func (self *Furnace) Update(dt float64) (completed bool) {
  // burn some coal and smelt the ore
}

I maintain a list of things that need to be regularly updated and getting something into that list is simply a matter of writing the Update function for it.

There’s a performance advantage of not having inheritance in Go. Instead of a vtable that is traversed hierarchically at runtime for every call, Go has a static sorted list of methods for a type and a static sorted list of methods for the receiving interface and it simply runs down each list in parallel to determine if the type meets the interface or not.

Developer support

In the interests of actually finishing this post and retaining a chance of people reading it, I'm going to wrap up by talking about some of the developer support that's available.

One of the most useful tools is the Go playground. This is a web-based Go environment that you can use to test code and share with others. Just like pastebin it gives you a short URL for a code snippet but let’s you run that code in-place. This is invaluable when learning, especially when asking question on the golang-nuts mailing list or on StackOverflow. The fact that everyone is discussing the same code and everyone can see that the code actually compiles and runs makes for much quicker answers.

Another invaluable tool is Gary Burd’s gopkgdoc, a documentation server. It takes advantage of Go’s built-in web based package management to generate documentation for Go packages. For example, here is the documentaton for the github.com/cznic/mathutil package: http://go.pkgdoc.org/github.com/cznic/mathutil, or for the standard image package: http://go.pkgdoc.org/image. I have it set up as a short search keyword in firefox so I can just type “go math” to get the documentation for the math package. The standard package library is very comprehensive.

If you’re learning go then there’s a good set of graded learning materials. Start with the Go Tour which is an interactive tour through the language with an embedded Go compiler so you can try the code out as you learn. Then you can progress onto the interactive code walkthroughs such a Channels and Goroutines or First Class Functions. Once you’ve got into actually writing code, the Effective Go document provides an overview of common idioms and practices and the language specification is very readable once you’re regularly using the language.

Finally, there are a couple of books that could be useful: Programming in Go by Mark Summerfield and A Complete Introduction to Go by Miek Gieben.

Update: Follow the discussion on Reddit and Hacker News

Permalink: http://blog.iandavis.com/2012/05/5-weeks-of-go/

Other posts tagged as golang, technology

Earlier Posts