Rich enums for Go.
go-enumr is a code generation tool for creating Rich Enums in Go. The name is a nod to both Rich enums and the Go convention of agent nouns (like Stringer)—it's the tool that makes your types work as enums.
Unlike standard Go enums (which are just integers), go-enumr allows you to use structs as enums. This enables you to bundle associated data—like labels, database IDs, configuration values, or color codes—directly with your enum identity.
A Rich Enum is an enumerated type where each variant carries its own immutable data. Instead of scattering your logic across multiple switch statements or lookup maps, you encapsulate the data within the enum value itself.
Standard Go enums are great for simple flags, but they fall short when you need associated data:
// Standard Go Enum
type Status int
const (
Pending Status = iota
Active
)
// Problem: How do I get the string label? The database ID? The color for the UI?
// You usually end up writing massive switch statements or lookup maps.Rich Enums solve this by keeping data together:
// go-enumr Rich Enum
type Status struct {
ID int
Label string
Color string
}
var (
Pending = Status{0, "Pending", "#FFA500"}
Active = Status{1, "Active", "#00FF00"}
)go-enumr automates the boilerplate needed to make these structs behave like proper enums, generating String(), MarshalText(), UnmarshalText(), and helper functions like Values() and Parse().
Use go-enumr when:
- Your enum variants have associated data (labels, config, database IDs).
- You want to keep that data co-located with the definition.
- You need to look up enum instances by their fields (e.g., find the status with ID 5).
Use stringer or go-enum when:
- You just need a simple list of names (nominal enums).
- You don't have any extra data attached to your enum values.
- You are fine with using
iotaor simple underlying types (int/string).
You can track go-enumr as a tool dependency in your go.mod file:
go get -tool github.com/jmfrees/go-enumr/cmd/enumrThen run it via go tool:
go tool enumr -type=MethodOr in your //go:generate directive:
//go:generate go tool enumr -type=Methodgo install github.com/jmfrees/go-enumr/cmd/enumr@latestThere are two ways to use go-enumr:
- Directive Mode (Recommended): Define your enum values in comments.
- Manual Mode: Define your enum values in a
varblock.
Use //enumr:<Name> comments to define your enum instances. The tool will generate the var block for you.
package payment
//go:generate enumr -type=Method
//enumr:CreditCard Code:CC Description:"Credit Card" IsCredit:true
//enumr:PayPal Code:PP Description:PayPal
//enumr:BankTransfer Code:BT Description:"Bank Transfer"
type Method struct {
Code string
Description string
IsCredit bool
}Run the generator:
go generate ./...This creates method_enum.go containing:
var (
CreditCard = Method{Code: "CC", Description: "Credit Card", IsCredit: true}
PayPal = Method{Code: "PP", Description: "PayPal"}
BankTransfer = Method{Code: "BT", Description: "Bank Transfer"}
)
// ... generated methods ...Syntax Rules:
Key:Valuesets a field (e.g.,Code:CC).Key:"Value with spaces"sets a string field with spaces.Key:truesets a boolean field (e.g.,IsCredit:true).Key:"[]string{\"a\", \"b\"}"sets complex types (slices, structs) by quoting the Go syntax.- Fields not specified default to their zero value.
If you need complex initialization (e.g., function calls, external imports) or want to document individual instances, you can define the variables yourself.
When to use Manual Mode:
- You need to use functions like
time.Date(). - You need to import other packages.
- You want to add godoc comments to specific enum instances.
package payment
//go:generate enumr -type=Method
type Method struct {
Code string
Created time.Time
}
var (
CreditCard = Method{"CC", time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)}
)go-enumr will detect the existing var block and generate the helper methods for it.
The tool generates the following for your type:
func (t Type) String() string: Returns the enum name (e.g., "credit_card") or the value of the field specified by-marshal-field.func (t Type) MarshalText() ([]byte, error): Implementsencoding.TextMarshaler.func (t *Type) UnmarshalText([]byte) error: Implementsencoding.TextUnmarshaler. Matches the string representation exactly.func TypeValues() []Type: Returns a slice of all enum instances.func ParseType(s string) (Type, error): Helper to parse a string into an enum instance.
-type: (Required) Comma-separated list of type names to generate code for.-marshal-field: (Optional) The name of the struct field to use for marshaling (e.g.,Code). If not provided, the enum instance name is used (transformed by-format).-format: (Optional) Casing format for the string representation (ignored if-marshal-fieldis used).- (empty) (default): Preserves the original casing.
snake_casecamelCasePascalCaseSNAKE_CASETitle Case
-output: (Optional) Output file name or directory. Defaults to<type>_enum.goin the package directory.-zero: (Optional) Allow the zero value (empty string) to be parsed as a valid enum value. Useful for optional fields that default to the zero value.
Since Go structs cannot be const, these enums are defined as var. While technically mutable, the convention is to treat them as immutable constants.
To more strictly enforce immutability, the recommended pattern is to use private fields in your struct definition and expose data via public getter methods.
//enumr:Free Price:0.0
//enumr:Pro Price:29.99
type Plan struct {
price float64 // private field
}
// Public getter prevents external modification
func (p Plan) Price() float64 {
return p.price
}This ensures that consumers of your package can read the data but cannot modify the enum fields directly.
This project is inspired by the excellent work of:
- stringer: The official Go tool for generating
String()methods. - go-enum: A powerful enum generator that supports parsing from comments.
go-enumr builds on these ideas but focuses specifically on the "Rich Enum" pattern, where the enum value itself is a struct containing data, rather than just an integer or string.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository.
- Create your feature branch (
git checkout -b feature/AmazingFeature). - Commit your changes (
git commit -m 'Add some AmazingFeature'). - Push to the branch (
git push origin feature/AmazingFeature). - Open a Pull Request.
MIT