Some things for enumerated constants in Go

May 27, 2014

The standard way to do C style enums in Go is with iota and a typed set of constants. This looks like this:

type SmtpCmd int
const (
    EHLO SmtpCmd = iota
    MAILFROM
    RCPTTO
    ....
)

This set of constants is perfectly fine in one sense, but it has a non-obvious drawback (at least for someone coming from a Python world): you can't tell an uninitialized instance of SmtpCmd from an EHLO value. Like all Go types, SmtpCmd has a zero value and since it's ultimately an integer the zero value is, well, 0. iota starts numbering from 0 so EHLO is also 0. Having your zero value overlap with a valid value that you can see normally has the possibility of hiding errors like uninitialized fields or unset return values. Instead of being glaringly obvious it will just look like your code is somehow incorrectly determining that you have an EHLO.

The simple fix for this is to introduce a dummy first value:

const (
    unsetCmd SmtpCmd = iota
    EHLO
    ....
)

(We make unsetCmd start with a lower case letter so that it won't be visible from outside the package. This may or may not be a good idea depending on whether outside people generate instances of SmtpCmd or just look at them.)

The other thing you can do in Go with a typed set of constants is to pretty-print them for the purposes of the %v formatting operator. All this takes is a String() method function defined for your type. Doing a minimal version that annotates the string value with type information is simple but perhaps not completely useful:

func (v SmtpCmd) String() string {
    return fmt.Sprintf("<SmtpCmd %d>", v)
}

More sophisticated versions are possible if you have either a source of number to name mapping information or just build one yourself. You probably don't need it to be strikingly efficient because you probably won't be using %v on your enumerated types very often.

(One thing you can do in even a simple version is specifically annotate unsetCmd and perhaps anything that's out of range for your actual defined constants.)

Sidebar: A potential trick with String() methods

In many cases being able to use %v on your package's values is most useful when debugging test failures. In addition, printing useful information about them may involve crusty code that you don't really like sitting around in the main package source.

As far as I can tell, you can actually put the String() method definitions in your *_test.go files and have them work. The *_test.go packages are an internal part of your package when built for tests, but I believe that stuff defined in them is not part of your package's exported functionality and the crufty and internals specific pretty-printing code can live with the rest of your testing code.

(Of course this may cause a certain amount of confusion someday, when your package internal tests print out nice looking and informative %v values for things while in your outside code they just gets eg basic numbers. This is your call to make. I honestly don't know which side I come down on right now, although I get a half-pass because I'm not writing a package I expect anyone else to ever use.)


Comments on this page:

By Mordy Ovits at 2014-05-27 11:00:51:

Per the Effective Go guide, the preferred way to "start" an iota-based const enum with a nonzero value is to use underscore to discard the first value: http://golang.org/doc/effective_go.html#constants

Written on 27 May 2014.
« Firefox, DRM, and reality
Yahoo Groups has a bad spam problem and they don't care »

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

Last modified: Tue May 27 00:43:18 2014
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.