We’ve recently been building the backend support for automatic iOS error reporting, which involves catching unhandled exceptions, bundling up the stack trace and related device environment data, and posting it to our API endpoint for processing. Unlike the other platforms we support, Apple’s mobile OS generates crash reports which contain the stack trace represented as a list of memory locations. Presenting this as useful information to the user means resolving those locations to the correct symbols, using information contained in the associated symbol files that are generated when Xcode builds an iOS application.
golang logo
In order for our service to achieve this, we needed a backend process that could inspect the raw crash reports against their symbol files and spit out a plain-English stack trace complete with line numbers and all the expected data. We chose Go to implement this worker process as it has quite a few interesting built-in libraries for dealing with Mach-O and DWARF files which are essential for building an iOS stack trace. Combined with Go’s lightweight concurrency mechanisms, neat syntax and handy language features, we were able to build the feature safely and in record time. In no particular order, here’s five things I found particularly enjoyable about using Go:

1. Single deployable binary

After your Go program is written and built it compiles down to a single binary that you can deploy as is. This means you don’t have to worry about having GRE v 1.6.19 or some other dependency set up on your machine, which is a revelation coming from… well, just about any other ecosystem.
As an aside, there’s been a bit of talk recently about Go’s dependency situation while developing. Yeah, it isn’t ideal yet (how to deal with transitive dependencies being one of the main points), but hopefully we’ll see package managers or language features that ease this pain point in the future.

2. Slices

Go’s slices are an abstraction on top of arrays. A point of difference from C is that arrays in Go are value types, which means that passing them around will copy all the elements of the array. Pass an array to a function, and it’ll receive a copy, which can be useful albeit expensive. Additionally, the size of an array is part of its type, and is fixed. Arrays act in a similar way to structs but with indexed fields. Slices allow you to reference a part of an array and pass around this reference, which is both more performant and idiomatic in Go. Slices also have variable length. They have element selection notation like Python’s slices, which may be familiar and useful.

3. Composition

Go is not strictly an OO language, as it doesn’t have a notion of classes (and subclassing), but it has types and methods (a function that takes a receiver parameter) that allow you to build in an object-oriented style. Go doesn’t have type hierarchies, but instead allows embedding of one type into another. This makes for a compositional style of development as opposed to inheritance. Combined with its functional feature set (first-class functions, higher-order functions, closures etc), this can makes for terse, readable code with minimal boilerplate.

4. Implicit Interfaces

Like other languages, interfaces in Go allow specification of behavior. Types can also implement multiple interfaces as you’d expect. The difference is that types implicitly satisfy interfaces rather than explicitly. This reduces coupling and allows for better reuse of code, but also means that any type that implements the methods that an interface specifies can be used wherever the interface is expected. For instance, the Handler interface in the http package has one method:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Any custom type which has a ServeHTTP method that takes an http.ResponseWriter and an http.Request is considered to implement the interface. Interfaces in Go are often comprised of one or two methods, making them a really low impact way to write and use modular code.

5. Goroutines

Go’s language-level concurrency features are pretty nice to use. Goroutines are commonly described as lightweight threads, but are more accurately viewed as functions that can run concurrently with other functions – i.e. having their statements interleaved. These interleaved functions exist in the same address space. If one goroutine issues a blocking call (for instance an I/O call) the others will continue to execute. And the syntax is about as elegant as you can get:

go list.Sort()

Any function prefixed by ‘go’ will run asynchronously without waiting for it to return. Goroutines are pretty high level and hide the potentially thorny thread implementation and concurrency issues from you. They also don’t specify where they will run – it may be on the main process (cooperative multitasking), or one or many other threads (spawned as needed upon blocking calls). The other crucial language feature is channels, which are a message-passing implementation that allows communication between goroutines. This message passing can also be synchronous or asynchronous. In these aspects Go’s pretty similar to Erlang, so if the latter language with a string type sounds like your jam, Go should be high up on your list of contenders for building your next network app or tool.

Final thoughts

Our program has behaved nicely during testing and staged deployment, and the code has proved to be easy for other team members to pick up. Barring any unforeseen disasters this tool will soon be a significant cog in the Raygun backend architecture and we’ll have the language foremost in our minds when other projects come up in future. It is still a young language and ecosystem which has its downsides, but for its age the API is mature and has served our needs well for the task we required of it. Feel free to add your own experiences with Go or similar technologies in the comments section below.

There’s also a Hacker News discussion page.