I think it’s valuable for game developers to know of a broad range of programming techniques and tools, and programming languages are one of our fundamental tools that strongly dictate how we phrase answers to the problems that we’re solving. With this in mind, I’d like to introduce Go, a language created by Google mainly for creating web services.
What is Go?
- Strongly typed (with dynamic casting), compiled language, garbage collected
- Made by Google for their servers, based on pragmatically solving problems
- Really good at concurrent stuff, pretty fast
- Designed to be productive (similar to Python, “batteries included” standard library)
- Structural interfaces, not inheritance
- Public /private by capitalization
- Built in unit testing, benchmarking, profiling, building, documentation server
- Compiles fast, cross platform
Website: http://golang.org
I think that the go tour on the official website is a really cool way to have a look at this language without having to install anything. Be sure to pay closest attention to goroutines and channels, since they’re the fundamental building block.
Good introductions / tutorials:
- Golang tour:
- Advanced Concurrency Patterns:
-
Performance
Talking about performance is always a bit of a strange… I’ve worked for a long time under the belief that programmer time is typically more valuable than using a potentially faster language, but this depends on the math of your scaling. Go is one of the fastest languages available for writing web servers, with the added advantage that it offers not only the IO based async / concurrency, but also an able solution for using multiple cores. You can almost certainly find things that outperform it in particular situations, but I tend to think that it’s a pretty safe high-performance option all round, without the worry of something like Ruby or Python that you’ll be running 30 times more nodes to compensate for your language choice, or worrying about refactoring because of the function call overhead on the hot path.
Using CSP as a concurrency paradigm also means that it’s much easier to scale out in the cloud – communication will work between many machines where mutexes won’t.
Tooling
I wish that Go had a full-featured editor with refactoring and debugging support on all operating systems.
There are some very nice features, like being able to import a profiling module that will enable a web endpoint for collecting and downloading profiles, built in unit test and benchmark support (unfortunately no built-in XUnit output format – ) – you’ll need GDB on a linux platform to get anything, which is really off-putting if you’re primarily based on Windows and Visual Studio.
Standard Library
You can go a very long way with Go’s standard library. It’s really only a few lines of code to run a web server. It has html templating built in, JSON support, image libraries + binary encoding support. Websockets are available as a supplementary library.
When you go further with web servers, I’ve found that the Revel framework is quite nice for fast iteration compile-as-you-save (it’s based on the Java “Play” framework) along with better featured routing & variable binding and a lot more features as Revel approaches a 1.0 version. There are others
Deployment
Go is designed to compile to a single, statically linked, standalone binary executable. This makes it very easy to deploy, at the cost of a bit of extra size. It does mean that individual libraries cannot be individually updated.
Cross platform compilation isn’t the easiest of things, and it falls apart if you have any native code. This isn’t the end of the world, as long as you have CI nodes running on VMs.
Version Control
Go has a remote package retrieval mechanism built in “go get”, but it relies on Git/Mecurial etc being installed on your machine. If you’re going to rely on any third party libraries, you’re going to have to expect to install these VCSes on at least one machine to get new versions.
While I think that it is possible to just get 3rd party packages direct at compile time at the latest version, in my opinion, it’s pretty dangerous to not have reproducible builds, which is problematic since there is no way currently to ask for a particular known-good revision. For my projects, I’m trying to get and check in every dependency.
There is currently quite a lot of ongoing discussion (and a number of community built solutions) for trying to deal with this issue, but no apparent wider consensus yet.
C interop
Go supports C based packages, which puts it in a similar position to Python and a number of equivalent languages.
Language
There are no real exceptions in Go – you’re supposed to use multi-value returns to allow error codes and handle them explicitly. This can make things quite verbose.
No RAII (ish) – Go uses defer (a bit like a function-based Scope Guard) to execute cleanup. This makes more sense when you understand that lifetime managing objects are generally sitting in a for{select {}} scope responding to messages. Once they exit that loop, they’re being shut down. This said, it might be possible to use runtime.SetFinalizer() for RAII with the understanding that cleanup will then be tied to when the GC runs on an object.
Unicode from the start – Go is based around UTF8, which is a blessing compared to the sheer confusion that I’ve seen in Python 2.7 and other places about encodings.
Closures – I find that closures are excessively handy for being able to encapsulate and bind things into a function signature that something else expects.
Dynamic casting – while Go is statically typed, it has a strong system for dynamic casting and reflection. This yields one of the nicest implementations of JSON marshalling/unmarshalling that I’ve seen, allowing it to fill in fields on typed structures from a given JSON input, both validating it and allowing you to use normal field access notation rather than doing a lot of casting and map lookups.
You support an interface in Go just by implementing the required function signatures on a structure, without needing to know about the interface at any time. To a certain degree, this is great: if you implement io.Reader, your object can now be used in a myriad of ways. This works fairly nicely with objects being passed around (A implements B, so can be passed to C(B)) but not quite so well with function signatures (if you return something that implements an interface, that’s not accepted if the required signature wants the interface returned).
No generics – while everything implements the empty interface, if you want to implement a particular container other than the given slices (vector/array) or maps, you’re not going to be able to get it to be reusable without it returning an interface that someone has to try and type assert back to what they put in. This is possibly worst with arrays of interfaces, where you’ll have to cast each item individually. You have to get used to this sort of thing though, since in order to use the sorting library, you’ll need to cast them to a type that implements Len, Swap and a compare function.
Go[tchas]
There are some things that they don’t mention much in the tutorials that I feel obliged to point out. Your mileage may vary.
- Channels can’t return tuples, so if you need errors and values as possible returns, you may need to pack them into a struct.
- If something is sending you values across a channel, and you’re consuming them in a range loop, be careful about exiting the loop early. Leaving the loop without fully consuming it is likely to leave a goroutine somewhere blocked while trying to send, and goroutines aren’t cleaned up in this kind of case. If this is likely you want to make sure that there is a way to Close the object that is sending prematurely.
- The standard pattern in Go is to loop processing messages using for{select{}} – be aware that ‘break’ works on select as well as for, so if you want to exit the loop, you need to use a labelled break, exit condition or return (making sure all cleanup is handled by deferred statements)
- Many tutorials tend to show a ‘quit’ channel that you send to and forget about. This can be a little dangerous and simplistic – if your deferred functions deadlock, or you didn’t break out of your loop properly, you’ll just leak your goroutine and any associated memory with it. With more complicated objects, I like to have a very carefully tested ‘quitblocker’ can block until exit has completed, and doesn’t have the vulnerability of potentially trying to send a close message to a channel that’s no longer being listened to on the service loop.
- map lookups return two values, always. value, wasPresent := myMap[key]. if !wasPresent, then value is uninitialized (zeroed in Go). Single assignment of just the value is valid, but infrequently safe/correct.
My Experience with it
I’ve been porting some of our internal infrastructure to Go from Python. Where we previously used a number of processes (and multiprocessing) in Python, we’re now using far fewer due to the increased ease of concurrency and more competent implementation of threading. I’ve also been able to add an http server to the mix, allowing me to interrogate the status & history live from a website, and potentially orchestrate the process.
I’ve found the libraries to be well featured, with one of the few things that I miss being a way to shut down a running http server by doing something other than exiting the program.
This isn’t a language for use in UIs or game clients, but it is an extremely competent language for making servers, infrastructure or parallel/distributed processing systems.