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.,
int
orstring
), 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-lint
withexhaustive
extension) support checking for exhaustiveswitch
statements 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)