hakaselogs

Notes mostly about software engineering and what I’m working on.

08 Apr 2021

Enum Stringer Interface optimisation in Go

The most idiomatic way of describing an enum type in Go is to use constants, often in conjunction with iota.

A pattern I’ve used to implement the Stringer interface for an enum type is to lookup the string representation in a slice. Consider this snippet from a project I’m working on:

// Status ...
type Status uint32

// List of possible status values
const (
  // The operation is known, but hasn't been decided yet
	Processing Status = iota
  // The operation will never be accepted
	Rejected
  // The operation was accepted
	Accepted
  ...
)

func (s Status) String() string {
  return []string{"processing", "rejected", "accepted"}[s]
}

The snippet above works correct but has a few glitches – passing Status(-2) or Status(300) will cause the String() method to panic, and when appending to the slice, the order of the enum constants has to be taken into consideration.

A way to optimise this is to use a map which provides O(1) access time rather than the linear O(n) time in the string slice version.

...
// Map of Status values back to their constant names for pretty printing.
var txStatuses = map[Status]string{
	Processing: "processing",
	Rejected:   "rejected",
	Accepted:   "accepted",
}

// String returns the ErrorCode as a human-readable name.
func (s Status) String() string {
	if s := txStatuses[s]; s != "" {
		return s
	}
	return fmt.Sprintf("Unknown Status (%d)", uint32(s))
}