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.
go get -u github.com/xybor-x/enum
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).
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
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
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
}
Note
All of the following functions can be used with any type of enum.
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
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
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
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
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
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
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 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
: Implementsjson.Marshaler
andjson.Unmarshaler
.SQL
: Implementsdriver.Valuer
andsql.Scanner
.
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
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
}
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.
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
}