The standard way to do C style
enum
s in Go is with
iota
and a typed set of constants. This looks like this:
type SmtpCmd intconst ( 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.)