Google+

5 Weeks of Go

21

23 May 2012 by Ian Davis

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

21 thoughts on “5 Weeks of Go

  1. db says:

    “loss of braces around conditions” -you mean parentheses?

  2. dwight says:

    I have not used Go; just interested in it. I like this article a lot.

    Thank you.

  3. taliesinb says:

    Nice blog post. I’m using Go for a performance-critical piece of a project and I’ve found it very pleasant to work with.

    Quick question: what do you mean, ‘traversed hierarchically’? My understanding was that in C++ the vtable is ready-to-go, because the traversal happens at compile time for each class.

    • Ian Davis says:

      I think in more dynamic languages such as Python or even Smalltalk the traversal happens at runtime, but I’m not a language expert.

      • Kannan says:

        Nope. Not even in dynamic languages.

      • tehwalrus says:

        this definitely happens in Objective C (source), although I’m not sure about python et al (might be done at ‘import’ time rather than actual-method-call time)

  4. Cedric says:

    “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.”

    I do not get this part.

    - inheritance (even multiple inheritance) does not prevent the compiler to know the offset of the proper vtable so that calling a virtual function requires only one indirection

    - determination if the type matches the interface does not occur at runtime (or so I hope).

    Are you sure you are not confusing runtime calls and compile time typechecks? If so, can you elaborate?

    • Ian Davis says:

      I’m basing my information on http://research.swtch.com/interfaces (the Method Lookup Performance section discusses it)

      • Roee says:

        Note the the language was “…program in a dynamic language like Smalltalk (or JavaScript, or Python, or …)” – you would be right if you were comparing to python or some other very dynamic language, but C++ virtual function calls cost two memory lookups (read the vtable pointer, read the function pointer from [vtable + fixed offset]) and indirect calling, same as it is in Go.

        In a language like python this code:
        a = SomeClass(); a.b(); a.b()
        doesn’t necessarily mean b() is the same function in both cases, by design since you can intercept the process of looking up what “b” is in runtime. In both C++ and Go this isn’t the case, so you can optimize the lookup process.
        As to why this is a good idea to have, it’s the reason you can do something like ctypes.windll.user32.MessageBoxA(0,”blah”,”blah”,0) in python, which will load user32.dll, get the address of MessageBoxA and call it dynamically.

        On an unrelated note – what you’re describing about duck-typing was on the list of features for C++0x for a long time (it was called ‘Concepts’) and even had a prototype implementation in GCC, but it was too hard to standardize and the standard became C++11 even after dropping that major feature.

  5. pikachu says:

    Two corrections:
    -Panic/recover is a mechanism for exceptions.
    -You can dynamically link using gccgo

  6. Go seems to have some great features, especially the way you can specify dependencies, but the lack of parameterized types is a dealbreaker for me

  7. Evan Farrer says:

    While go doesn’t have exceptions it does have exception like mechanism through panic/recover. The rule is that exceptions will never cross a library boundary. There are some cases where exceptions can be a better choice for error handling and Go provides an exception like mechanism for those cases.

  8. Evan Farrer says:

    Go isn’t OO like C++ or Java but it is still nicely supports the OO paradigm. In fact it supports encapsulation and polymorphism just fine via private (lower-case) identifiers and methods on types that match an interface respectively. Additionally you can simulate inheritance via structural embedding. The issue is not that these features are nice, but that there is a fallacy that they are only available in OO languages.

  9. rembo666 says:

    I disagree that omission of dynamically-loaded-libraries is a good thing. In fact, I think that was one of the features that simply “did not make it into 1.0″.

    While I share your sentiment that updating a single library can break the whole application, I’d rather have the option to do it.

    However, the place where I find dlls shine is to have plugin modules discoverable at runtime. You could theoretically use a different service/process for each one of the plugins, but the overhead of that can be unacceptable in certain high-performance applications.

  10. Paul says:

    Go seems to have some great features, especially the way you can specify dependencies, but the lack of parameterized types is a dealbreaker for me

  11. [...] majority of reviews have been very good at praising the ever loving crap out of this language, and since [...]

  12. Eugene says:

    While all the articles I have read so far state that Go has no exceptions mechanics, I still fail to see how panic/recover differ from exceptions.

    For me, it looks like: panic is throw, defer is an automatic destructor for stack unwinding, and defer/recover is try/catch. That simple.

  13. Ian.

    Discounting availability of programmers, or even including that if Go is quick to master – would you be (or will you be) comfortable using Go in production.

    Or put another way – what language would you be most likely to use in your next (hypothetical of course ;) ) globally targeted startup.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: