rothwell.im

by Jonathan Rothwell

Go, grown up

Warning: this is a boring “technical” post.

I dug out the source code to Screttler this morning, given that I’ve neglected it substantially over the last few months. For those who are unaware, Screttler is a random password generator with a difference: instead of using dictionary words or random sequences of letters and numbers, Screttler creates nonsense words by chaining vowel and consonant phonemes together, and adding these to numbers and symbols et cetera. Here’s a quick, and rubbish, diagram (please excuse the appalling handwriting):

Each password consists of numbers, symbols and nonsense words, consisting of syllables made from vowels and consonants.

Screttler is written in Go. Go reached maturity with the release of version 1.0 on Wednesday, so I thought now would be as good a time as any to see how the language had matured, and also to ensure that Screttler worked with it.

Compiler tools

The first thing that struck me was how convenient Go has become to use. When I wrote Screttler, each source file had to be compiled and linked manually, á la gcc. For instance:

$ 8g teapot.go
$ 8l teapot.8
$ ./teapot
I'm a teapot!

This created additional headaches when writing makefiles, because the command for the compiler varied between processor architectures. x86 architectures used 8g; 64-bit architectures used 6g; to produce ARM machine code, you had to run 5g. This was such a hassle that Screttler’s README file includes the following disclaimer:

(This will only work out-of-the-box on x64 architecture Unixes: if you are using an x86 distribution, you will need to replace every instance of 6g and 6l in the makefile with 8g and 8l. This is because Go uses different compiler commands for every processor architecture. Similar alterations must be made for ARM, which uses 5g and 5l.)

With Go 1.0, almost all functionality has been brought under the go command, which is a godsend in terms of convenience. Now, a single source file can be compiled and run with an exceptionally simple:

$ go run teapot.go
I'm a teapot!

There are more convenient ways to deal with larger projects, too. It took me less than five minutes to set up a workspace with three directories, src, pkg and bin, such that an entire package can be installed with:

$ go install mygo/teapot

Aside from obviating the need for makefiles (and good riddance—they’re a nuisance) this actually encouraged me to separate Screttler into two separate packages. The actual password generation engine now lives in its own package, gentler, whilst the invocation command is now screttle.

From an administrative standpoint, this is extremely convenient, and also substantially easier to understand than, for instance, the unholy mess of Ruby’s gem system.

Syntax

For those unfamiliar with Go, this is what a trivial program looks like:

package main
import "fmt"
func main() {
fmt.Printf("Hello, world; or %s\n", chineseWorld())
}
func chineseWorld() string {
return "世界"
}

As you can see, Go looks a lot like C, but with some obvious differences:

  • semicolons aren’t required (Go’s compiler automatically determines where one statement ends and the next begins);
  • Unicode support is baked in;
  • functions are prefixed with the func keyword;
  • the type declaration follows the variable/function identifier, rather than coming before it;
  • strings are first-class types.

This ultimately means that Go is familiar, but with a few added conveniences that eliminate many of the traditional “gotchas” many new programmers encounter when learning C. Occasionally, however, I did find myself trying to declare int x; as opposed to var x int, and unnecessarily terminating lines with semicolons!

The syntax is streamlined in many places, eliminating unnecessary variations. For instance, all do and while loops are now variations on the for loop.

// traditional for
for i := 0; i < 6; i++ {
DoSomething(i)
}
// for in place of a while loop
for lines > 500 {
DeleteLine()
}
// forever loop
for {
Bellow()
}

The resulting syntax is exceptionally clean. For someone such as myself who has, for educational purposes, been hacking with the abominable hodgepodge of Java for the last six months, coding in Go is a breath of fresh air.

Goroutines

Go supports concurrency, and does it extremely well using something called goroutines. Implementing goroutines is as simple as this:

func main() {
go Complex() // Complex() will now be executed in the background
for {
fmt.Printf("Something expensive is happening...\n")
time.Sleep(500 * time.Millisecond)
}
}
func Complex() {
for {
DoSomethingExpensive()
}
}

That’s literally all there is to it. Threading is handled transparently in the background: all you need to do is use go AnyFunction() and it’ll start chugging away behind the scenes. In many ways, the go keyword is a lot like the & on Unix shells, sending the task to the background without any fuss.

Messaging between goroutines is done along channels.

c := make(chan int) // make a channel of integers
func main() {
go producer()
var i int
for {
i <- c // receive whatever's on channel c
fmt.Printf("Integer %i", i)
}
}
func producer() {
for {
c <- 7 // send the number 7 to channel c
}
}

Goroutines also led to an interesting discovery when I was working on beating Screttler into the new package format.

The odd little select construct

Go has a select statement that I stumbled across when trying to determine where pseudorandom number generation had gone (the rand package has been deprecated—in its place there are two rand packages, one under crypto and the other under math.)

Unlike, for instance, Visual Basic, which just calls its switch construct a Select, the select statement in Go has a distinct use. To quote the Go Programming Language Specification:

A "select" statement chooses which of a set of possible communicationswill proceed. It looks similar to a "switch" statement but with the cases all referring to communication operations.

Particularly interesting is this property of Go’s select:

If multiple cases can proceed, a uniform pseudo-random choice is made to decide which single communication will execute.

This turned out to be extremely useful. A lot of the code I’d written in the original Screttler was clunkily declaring hard-coded arrays of phonemes, slicing them, randomly selecting a suitable index, randomly deciding if that was enough syllables, and randomly deciding which type of token (word, number, symbol etc.) to add next.

This was excruciatingly un-idiomatic and fiddly. The use of goroutines and the select block, however, have made it possible for me to replace a convoluted mess of randomiser functions with a few functions such as this:

// GetToken perpetually spits out string tokens to the token channel.
func GetToken() {
for {
select {
case tc <- Word(true):
case tc <- Word(false):
case tc <- Number():
}
}
}

This property has made pseudorandom selection in Go highly convenient. I’ve managed to write an entire program dependent on randomness without actually importing a randomiser package at any point.

Summary

Of course, this godsend of the select block will probably have a sting in its tail. I can’t imagine, for instance, that you’d be able to seed the randomiser with some genuinely random data pulled, for instance, from random.org. On the other hand, it meant that I was effectively able to rewrite the entire program in less than eight hours.

This is primarily why I like Go so much. Although, in some places, it’s a little quirky—and frustratingly inconsistent, in areas—on the whole, the process of using Go to write real, functioning code is fun. I haven’t had this much fun programming since when I was originally learning C in 2005, because it feels like I’m actually coding, rather than hacking around the language’s quirks.

I’ll be replacing the existing Screttler GitHub repository with the Go 1.0 compliant version soon.

In the meantime, although it’s not going to be to everyone’s taste, if you’re even remotely interested, I urge you to install the Go toolchain and experiment with the language. It’ll be a breath of fresh air if you’ve been trapped with languages like C++, Java and Python due to work or education commitments.