Skip to content

xybor-x/enum

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, powerful, and dependency-free enums for Go with zero code generation!

Tip

This is just a ⚡ quick tutorial for general use cases. See more advanced features at the documentation.

🔧 Installation

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

⚡ Quick start

Define enums

package main

import "github.com/xybor-x/enum"

type role any
type Role = enum.WrapEnum[role]

const (
    RoleUser Role = iota
    RoleAdmin
)

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

Caution

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

Usage

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Role Role `json:"role"`
}

func main() {
    // Print out the string representation of enum.
    fmt.Println(RoleAdmin) // Output: admin

    // Serialize a user to json.
    user := User{Role: RoleUser}
    data, _ := json.Marshal(user) 
    fmt.Println(string(data)) // Output: {"role": "user"}
}

Nullable fields

package main

import (
    "encoding/json"
    "fmt"
)

// NullRole is similar to sql.NullXXX, designed to handle nullable SQL and JSON fields.
type NullRole = enum.Nullable[Role]

type User struct {
    Role NullRole `json:"role"`
}

func main() {
    // Serialize a nullable role with a non-null value.
    user := User{Role: NullRole{Enum: RoleUser, Valid: true}}
    data, _ := json.Marshal(user) 
    fmt.Println(string(data)) // Output: {"role": "user"}

    // Serialize a nullable role with a null value.
    data, _ = json.Marshal(User{})
    fmt.Println(string(data)) // Output: {"role": null}
}

Integrate with protobuf

Refer to the Integration Guide for details.

Suppose we have a protobuf enum defined as follows:

// Code generated by protoc-gen-go.
package proto

type Role int32

const (
	Role_User  Role = 0
	Role_Admin Role = 1
)

...

We can integrate them into xybor-x/enum. Here's an example:

package main

import (
    "path/to/proto"
    "github.com/xybor-x/enum"
)

type Role = enum.WrapEnum[proto.Role]

const (
    RoleUser Role = iota
    RoleAdmin
)

func init() {
    // Map the enum to protobuf enum value.
    enum.Map(RoleUser, proto.Role_User)    
    enum.Map(RoleAdmin, proto.Role_Admin)
    enum.Finalize[Role]()
}

func main() {
    // Convert from the protobuf enum to the Role enum.
    role, ok := enum.From[Role](proto.Role_User)
    // ok == true && role == RoleUser

    // Convert from the Role enum to the protobuf enum.
    role = RoleAdmin.To()
    // role == proto.Role_Admin

    // The string representation of these enums is inherited from proto.Role.
    fmt.Println(RoleUser) // Output: User
}

📈 Performance

While it's true that the xybor-x/enum approach will generally be slower than the code generation approaches, I still want to highlight the difference.

The benchmark results are based on defining an enum with 10 values at bench.

Code generation xybor-x/enum
ToString 6 ns 17 ns
FromString 15 ns 22 ns
json.Marshal 113 ns 148 ns
json.Unmarshal 147 ns 144 ns
SQL Value 29 ns 38 ns
SQL Scan (bytes) 29 ns 41 ns
SQL Scan (string) 15 ns 22 ns