Skip to content
/ enum Public

Elegant and powerful Go enums with zero code generation

License

Notifications You must be signed in to change notification settings

xybor-x/enum

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

22 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Go Reference GitHub top language GitHub go.mod Go version GitHub release (release name instead of tag name) GitHub Repo stars

Golang

βš™οΈ Go Enum

Elegant and powerful enums for Go with zero code generation!

Warning

Please keep in mind that xybor-x/enum is still under active development and therefore full backward compatibility is not guaranteed before reaching v1.0.0.

πŸ”§ Installation

go get -u github.com/xybor-x/enum

πŸ“‹ Features

All of the following enum types are compatible with the APIs provided by xybor-x/enum.

Basic enum (#) Wrap enum (#) Safe enum (#)
Built-in methods No Yes Yes
Constant enum (#) Yes Yes No
Serialization and deserialization (#) No Yes Yes
Type safety (#) No Basic Strong

Caution

Enum definitions are not thread-safe. Therefore, they should be finalized during initialization (at the global scope).

⭐ Basic enum

The basic enum (a.k.a iota enum) is the most commonly used enum implementation in Go.

It is essentially a primitive type, which does not include any built-in methods. For handling this type of enum, please refer to the utility functions.

Pros πŸ’ͺ

  • Simple.
  • Supports constant values (#).

Cons πŸ‘Ž

  • No built-in methods.
  • No type safety (#).
  • Lacks serialization and deserialization support.
type Role int

const (
    RoleUser Role = iota
    RoleAdmin
)

func init() {
    enum.Map(RoleUser, "user")
    enum.Map(RoleAdmin, "admin")
    enum.Finalize[Role]() // Optional: ensure no new enum values can be added to Role.
}

⭐ WrapEnum

WrapEnum offers a set of built-in methods to simplify working with enums.

Pros πŸ’ͺ

  • Supports constant values (#).
  • Provides many useful built-in methods.
  • Full serialization and deserialization support out of the box.

Cons πŸ‘Ž

  • Provides only basic type safety (#).
// Define enum's underlying type.
type role any

// Create a WrapEnum type for roles.
type Role = enum.WrapEnum[role] // NOTE: It must use type alias instead of type definition.

const (
    RoleUser Role = iota
    RoleAdmin
)

func init() {
    enum.Map(RoleUser, "user")
    enum.Map(RoleAdmin, "admin")
    enum.Finalize[Role]() // Optional: ensure no new enum values can be added to Role.
}

type User struct {
    ID   int  `json:"id"`
    Role Role `json:"role"`
}

func main() {
    // WrapEnum has many built-in methods for handling enum easier.
    data, _ := json.Marshal(RoleUser) // Output: "user"
    fmt.Println(RoleAdmin.IsValid())  // Output: true
}

⭐ SafeEnum

SafeEnum defines a strong type-safe enum. Like WrapEnum, it provides a set of built-in methods to simplify working with enums.

The SafeEnum enforces strict type safety, ensuring that only predefined enum values are allowed. It prevents the accidental creation of new enum types, providing a guaranteed set of valid values.

Pros πŸ’ͺ

  • Provides strong type safety (#).
  • Provides many useful built-in methods.
  • Full serialization and deserialization support out of the box.

Cons πŸ‘Ž

  • Does not support constant values (#).
// Define enum's underlying type.
type role any

// Create a SafeEnum type for roles.
type Role = enum.SafeEnum[role] // NOTE: It must use type alias instead of type definition.

var (
    RoleUser  = enum.NewSafe[Role]("user")
    RoleAdmin = enum.NewSafe[Role]("admin")
)

func main() {
    // SafeEnum has many built-in methods for handling enum easier.
    data, _ := json.Marshal(RoleUser) // Output: "user"
    fmt.Println(RoleAdmin.IsValid())  // Output: true
}

πŸ’‘ Utility functions

Note

All of the following functions can be used with any type of enum.

FromString

FromString returns the corresponding enum for a given string representation, and whether it is valid.

role, ok := enum.FromString[Role]("user")
if ok {
    fmt.Println(role) // Output: 0
} else {
    fmt.Println("Invalid enum")
}

FromInt

FromInt returns the corresponding enum for a given int representation, and whether it is valid.

role, ok := enum.FromInt[Role](42)
if ok {
    fmt.Println(role)
} else {
    fmt.Println("Invalid enum") // Output: Invalid enum
}

IsValid

IsValid checks if an enum value is valid or not.

fmt.Println(enum.IsValid(RoleUser))  // true
fmt.Println(enum.IsValid(Role(0)))   // true
fmt.Println(enum.IsValid(Role(42)))  // false

ToString

ToString converts an enum to string. It returns <nil> for invalid enums.

fmt.Println(enum.ToString(RoleAdmin))  // Output: "admin"
fmt.Println(enum.ToString(Role(42)))   // Output: "<nil>"

ToInt

ToInt converts an enum to int. It returns the smallest number of int for invalid enums.

fmt.Println(enum.ToInt(RoleAdmin))  // Output: 1
fmt.Println(enum.ToInt(Role(42)))   // Output: -2147483648

All

All returns a slice containing all enum values of a specific enum type.

for _, role := range enum.All[Role]() {
    fmt.Println("Role:", enum.ToString(role))
}
// Output:
// Role: user
// Role: admin

πŸ”… Constant support

Some static analysis tools support checking for exhaustive switch statements in constant enums. By choosing an enum with constant support, you can enable this functionality in these tools.

πŸ”… Serialization and deserialization

Serialization and deserialization are essential when working with enums, and our library provides seamless support for handling them out of the box.

Warning

Not all enum types support serde operations, please refer to the features.

Currently supported:

  • JSON: Implements json.Marshaler and json.Unmarshaler.
  • SQL: Implements driver.Valuer and sql.Scanner.

πŸ”… Type safety

The WrapEnum prevents most invalid enum cases due to built-in methods for serialization and deserialization, offering basic type safety.

However, it is still possible to accidentally create an invalid enum value, like this:

moderator := Role(42) // Invalid enum value

The SafeEnum provides strong type safety, ensuring that only predefined enum values are allowed. There is no way to create a new SafeEnum object without explicitly using the NewSafe function or zero initialization.

moderator := Role(42)          // Compile-time error
moderator := Role("moderator") // Compile-time error

πŸ”… Extensible

Extend basic enum

Since this enum is just a primitive type and does not have built-in methods, you can easily extend it by directly adding additional methods.

type Role int

const (
    RoleUser Role = iota
    RoleMod
    RoleAdmin
)

func init() {
    enum.Map(RoleUser, "user")
    enum.Map(RoleMod, "mod")
    enum.Map(RoleAdmin, "admin")
    enum.Finalize[Role]()
}

func (r Role) HasPermission() bool {
    return r == RoleMod || r == RoleAdmin
}

Extend WrapEnum

WrapEnum has many predefined methods. The only way to retain these methods while extending it is to wrap it as an embedded field in another struct.

However, this approach will break the constant-support property of the WrapEnum because Go does not support constants for structs.

You should consider extending Basic enum or Safe enum instead.

Extend SafeEnum

SafeEnum has many predefined methods. The only way to retain these methods while extending it is to wrap it as an embedded field in another struct.

xybor-x/enum provides the NewExtendedSafe function to help create a wrapper of SafeEnum.

type role any
type Role struct { enum.SafeEnum[role] }

var (
    RoleUser  = enum.NewExtendedSafe[Role]("user")
    RoleMod   = enum.NewExtendedSafe[Role]("mod")
    RoleAdmin = enum.NewExtendedSafe[Role]("admin")
    _         = enum.Finalize[Role]()
)

func (r Role) HasPermission() bool {
    return r == RoleMod || r == RoleAdmin
}