Some notes on my first experience with Go

February 21, 2013

I've finally wound up writing my first Go program. The program is a Go version of what seems to have turned into my standard language test program, namely a netcat-like program that takes standard input, sends it off to somewhere over the network, and writes to standard out what it gets back from the network. Partly because Go made it easy and partly due to an excess of new thing enthusiasm the program grew far beyond my initial basic specifications.

(I'm somewhat bemused but a netcat-like program really has become a standard program I write in new languages and to try out things like new buffering libraries. It's actually not a bad test.)

On the whole the experience was quite pleasant. The specific need I had is something I normally would have handled with a Python program and writing my Go program was not particularly much more work and bookkeeping than the Python equivalent would have been (it took much longer to write because I was semi-learning Go as I went and I already know Python). The code has reasonably few variable declarations and most of them are non-annoying; Go's := idiom really helps with this since it means that in many circumstances you don't have to declare a variable or specifically name its type.

One important thing I wish I'd know at the start is that you should ignore most everything the Go documentation overview pages tells you about what to read. Effective Go is in practice the quick guide to Go for C programmers, or at least for C programmers who have some general idea about Go to start with, and is the closest thing Go has to Python's excellent tutorial. The language reference is overly detailed and too hard to read for learning and the interactivity of the beginning tutorial makes it completely unsuitable for quick starts.

One of the reasons that I got as far as I did as fast as I did is that Go's networking library has a relatively high-level view of the world. There is no Python equivalent of Go's net.Dial() or net.Listen() APIs, at least not in the standard library; the existence of both of them made handling an absurdly wide variety of network protocols basically trivial (along with a bunch of complexity of hostname and port number lookups). On the flipside this API is not complete (especially in Go 1.0) and has a number of really annoying omissions. This is especially frustrating since I have the (Go) source for the net package and can see perfectly well that what I want access to already exists in the package; it's just not exported and (unlike Python) you can't fish into a package to grab stuff yourself.

My code wound up using goroutines and channels, although in a relatively basic way. Designing program flow in terms of channels definitely took several attempts before I had everything sorted out cleanly; earlier versions of the code had all sorts of oddities before I sorted out exactly what I wanted and how to express that in channel data flows. My broad takeaway from this experience is that it's very important to think carefully about what you want to do before you start eagerly designing a complex network of channels and goroutines. It was easy for me to get distracted by the latter and miss an obvious, relatively simple solution that was under my nose.

My feelings about channels and goroutines are mixed. On the one hand I think that using them simplified the logic of my code (and made it much easier to support TLS), even if it took a while to sort out that logic. On the other hand having to use goroutines is responsible for a serious wart in one aspect of the program, a wart I see no way around; the wart arises because there's no way for outside code to force a goroutine blocked in IO to gracefully abort that IO (this is a fundamental issue with channels).

This is rambling long enough as it is, so I think that I will save my language disagreements for another day. Well, except to say that I think that the standard Go package for parsing arguments and argument flags handles command line options utterly the wrong way and I need to get a real argument parsing package before I write another Go command.

(Go's standard flag package apparently follows some argument parsing standard that Google likes. It is pretty non-Unixy while looking just enough like normal Unix argument handling to fool you.)

Written on 21 February 2013.
« The meaning of listen(2)'s backlog parameter
Go: using type assertions to safely reach through interface types »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Thu Feb 21 02:50:20 2013
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.