What is enum?
An enum, short for enumeration, is a special data type that represents a set of named values. It is used to define a collection of constant values that are conceptually related, improving code readability and reducing errors caused by the use of arbitrary literal values.
// Enum in Java
enum TrafficLight {
RED, YELLOW, GREEN
}
# Enum in Python
from enum import Enum
class TrafficLight(Enum):
RED = 1
GREEN = 2
BLUE = 3
Enum in Go
Go doesn’t support enum natively. However, there is a popular way to define an enum in Go is using iota approach.
package main
type TrafficLight int
const (
RED TrafficLight = iota // 0
GREEN // 1
BLUE // 2
)
func main() {
fmt.Println(RED) // Output: 0
}
However, there are some problems when dealing with enum in this way:
- Lack of Built-in Methods: No direct support for features like listing all enum values or converting between strings and enums.
-
Limited Type Safety: Enums are typically represented using basic types (e.g.,
intorstring), which increases the risk of unintended assignments. - Serialization and Deserialization Complexity: Mapping enums to and from formats like JSON requires additional handling.
The xybor-x/enum library
The xybor-x/enum libray provides elegant, easy-to-use, and powerful solutions for Go enum with no code generation.
There are some types of enum which you can work with xybor-x/enum, please choose the most suitable one.
Basic enum
Pros 💪
- Simple.
- Supports constant values.
Cons 👎
- No built-in methods.
- No type safety.
- Lacks serialization and deserialization support. Like the traditional enum, the basic enum has no built-in methods. But you can use utility functions of xybor-x/enum to handle this type of enum.
package main
type Role int
const (
RoleUser Role = iota
RoleAdmin
)
func init() {
enum.Map(RoleUser, "user")
enum.Map(RoleAdmin, "admin")
// Optional: ensure no new enum values can be added to Role.
enum.Finalize[Role]()
}
func main() {
// Print the corresponding string.
fmt.Println(enum.ToString(RoleUser)) // Output: user
// Print out all valid enums.
fmt.Println(enum.All[Role]()) // Output: [0 1]
// Parse an enum from int.
r1, ok := enum.FromInt[Role](1)
fmt.Println(ok) // Output: true
fmt.Println(enum.ToString(r1)) // Output: admin
// Parse an enum from string.
r2, ok := enum.FromString[Role]("admin")
fmt.Println(ok) // Output: true
fmt.Println(r2) // Output: 1
// Serialize json.
data, err := enum.MarshalJSON(RoleUser)
fmt.Println(err) // Output: nil
fmt.Println(string(data)) // Output: "user"
}
WrapEnum
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.
package main
// Only need to change the two following lines fromthe Basic enum.
type role any
type Role = enum.WrapEnum[role]
const (
RoleUser Role = iota
RoleAdmin
)
func init() {
enum.Map(RoleUser, "user")
enum.Map(RoleAdmin, "admin")
// Optional: ensure no new enum values can be added to Role.
enum.Finalize[Role]()
}
func main() {
// Print the corresponding string. No need to use enum.ToString.
fmt.Println(RoleUser) // Output: user
// Print out all valid enums.
fmt.Println(enum.All[Role]()) // Output: [user admin]
// Parse an enum from int.
r1, ok := enum.FromInt[Role](1)
fmt.Println(ok) // Output: true
fmt.Println(r1) // Output: admin
// Parse an enum from string.
r2, ok := enum.FromString[Role]("admin")
fmt.Println(ok) // Output: true
fmt.Println(r2) // Output: admin
// Now you can use json.Marshal instead of enum.MarshalJSON.
data, err := json.Marshal(RoleUser)
fmt.Println(err) // Output: nil
fmt.Println(string(data)) // Output: "user"
}
WrapEnum is the most suitable enum for general cases. However, it only provides basic type safety. If you want a stricter one, consider using SafeEnum.
// WrapEnum cannot prevent this type of invalid declaration.
// Consider using SafeEnum.
r := Role(42)
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.
Why is constant-support important?
Some static analysis tools (such as
nogo for bazel,golangci-lintwithexhaustiveextension) support checking for exhaustiveswitchstatements in constant enums. By choosing an enum with constant support, you can enable this functionality in these tools.
package main
type role any
type Role = enum.SafeEnum[role]
var (
RoleUser = enum.NewSafe[Role]("user")
RoleAdmin = enum.NewSafe[Role]("admin")
// Optional: ensure no new enum values can be added to Role.
enum.Finalize[Role]()
)
func main() {
// You cannot create an enum like that, it causes a compile-time error.
// r := Role(42)
// r := Role("moderator")
// Print the corresponding string. No need to use enum.ToString.
fmt.Println(RoleUser) // Output: user
// Print out all valid enums.
fmt.Println(enum.All[Role]()) // Output: [user admin]
// Parse an enum from int.
r1, ok := enum.FromInt[Role](1)
fmt.Println(ok) // Output: true
fmt.Println(r1) // Output: admin
// Parse an enum from string.
r2, ok := enum.FromString[Role]("admin")
fmt.Println(ok) // Output: true
fmt.Println(r2) // Output: admin
// Now you can use json.Marshal instead of enum.MarshalJSON.
data, err := json.Marshal(RoleUser)
fmt.Println(err) // Output: nil
fmt.Println(string(data)) // Output: "user"
}
References
xybor-x/enum: https://github.com/xybor-x/enum
Medium: https://medium.com/@huykingsofm/enum-handling-in-go-a2727154435e
Vietnamese viblo: https://viblo.asia/p/cac-van-de-cua-go-enum-va-cach-giai-quyet-voi-xybor-xenum-Yym401A9J91

Top comments (0)