Getting started with Golang Enums
Not directly. Enums in Golang are typically declared as sets of unique integer constants, often with iota. However, you can map strings to enums and vice versa by creating custom methods or look-up maps, allowing you to work with strings as if they were enum values in practice.
In this tutorial, we will embark on an enlightening journey to explore the concept of enums in Golang, a powerful feature that enhances the readability and reliability of your code. Enums, or enumerated types, allow us to define a type by a set of named constants, making our code more expressive and aligned with the domain model. Despite Golang not having an explicit enum keyword, the language provides elegant ways to implement enums, leveraging constants and the iota enumerator.
Our expedition will navigate through essential territories such as the creation of Golang enums, advanced techniques, operations, integration with structs, and JSON, amongst other crucial topics. Each segment is meticulously crafted to unravel the practical aspects and best practices, ensuring a comprehensive understanding of Golang enums in real-world applications. Let's delve deep into the world of Golang enums and uncover the powerful paradigms they introduce to the Golang ecosystem.
Basic Concepts of Enums
Before diving into creating enums in Golang, it’s essential to grasp some foundational concepts. One of the cornerstones when dealing with enums in Golang is iota
. iota
is a predeclared identifier that represents the untyped integer ordinal number of a constant in a given enumeration.
Let’s delve into understanding the type and value when defining a golang enum. Enumerated constants are created by using the const
keyword, followed by an optional identifier, and an assignment that often uses iota
.
Here is a simple example illustrating the use of iota
in defining a Golang enum:
package main
import "fmt"
// Declaring an enum type Weekday
type Weekday int
// Enumerating the days of the week
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
// Displaying the enum values
fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
}
In this example, a new type Weekday
is declared, and days of the week are enumerated using iota
. The value of iota
increases automatically, starting from 0, assigning each constant a unique value, mimicking the behavior of an enum.
Creating Enums in Golang
Creating a Golang enum involves defining a set of unique constants that represent the possible values of the enum. Golang does not have native enum support, but you can create enums using constants, and iota
plays a crucial role in simplifying the declaration of sequential values.
1. Using Constants
Here is how you can create an enum by using constants:
package main
import "fmt"
// Declaring constants for representing seasons
const (
Spring = "spring"
Summer = "summer"
Autumn = "autumn"
Winter = "winter"
)
func main() {
fmt.Println(Spring, Summer, Autumn, Winter)
}
In this code snippet, seasons are represented as constants of string type, embodying a basic form of a Golang enum.
2. Using iota
iota
can be used to create more advanced enums, as illustrated below:
package main
import "fmt"
// Declaring an enum type Season
type Season int
// Enumerating the seasons
const (
Spring Season = iota
Summer
Autumn
Winter
)
func main() {
// Displaying the enum values
fmt.Println(Spring, Summer, Autumn, Winter)
}
In this example, iota
is used to automatically assign incremental values to the constants, streamlining the Golang enum creation process and making the code cleaner and more maintainable.
Advanced Enum Techniques
Navigating through advanced techniques can augment the utility and flexibility of enums in Golang. These techniques include skipping values, incorporating arbitrary expressions, and employing shifts to create bitmask enums. Each of these methods allows for a tailored implementation of golang enums based on the specific needs of your application.
1. Skipping Values
In certain cases, you might want to skip values in an enum. Golang allows you to do this effortlessly:
package main
import "fmt"
// Declaring an enum type
type Status int
// Enumerating with skipped values
const (
Active Status = iota + 1
Inactive
Deleted = iota + 5
Suspended
)
func main() {
fmt.Println(Active, Inactive, Deleted, Suspended)
}
This code sets Active
to 1, Inactive
to 2, skips a few values, sets Deleted
to 6, and Suspended
to 7.
2. Arbitrary Expressions
Golang enums can be assigned values resulting from arbitrary expressions, allowing for enhanced flexibility:
package main
import "fmt"
// Declaring an enum type
type Grade int
// Enumerating with arbitrary expressions
const (
A Grade = (iota+1)*10
B
C
D
F = iota*5
)
func main() {
fmt.Println(A, B, C, D, F)
}
Here, each constant is assigned a value based on an arbitrary expression involving iota
, allowing for customized golang enum values.
3. Using Shifts for Bitmask Enums
Bitmask enums are powerful tools for managing sets of boolean flags. Using bitwise shifts with iota
enables this:
package main
import "fmt"
// Declaring an enum type
type Permissions int
// Enumerating with shifts for bitmask
const (
Read Permissions = 1 << iota
Write
Execute
)
func main() {
fmt.Printf("Read: %b, Write: %b, Execute: %b\n", Read, Write, Execute)
}
In this bitmask golang enum example, each constant represents a permission, assigned a value that is a power of two. This allows them to be combined and manipulated using bitwise operations, providing a robust system for handling permissions or any set of toggles/flags in an application.
Enum Operations
Golang enums, while not a native feature, become powerful when we understand how to operate on them effectively. Enum operations such as comparison and conversion/parsing are fundamental in manipulating and utilizing enums in Golang effectively. These operations enhance the versatility and usability of the golang enums in various contexts within an application.
1. Comparison Operations
Comparison operations are essential to determine the equality or order of enum values. Here’s how you can perform comparison operations with Golang enums:
package main
import "fmt"
// Declaring an enum type
type Size int
// Enumerating sizes
const (
Small Size = iota
Medium
Large
)
func main() {
if Medium == 1 {
fmt.Println("Medium size is selected.")
}
if Large > Small {
fmt.Println("Large size is greater than small size.")
}
}
In this code snippet, we are comparing different sizes within the Size
golang enum, allowing for conditional logic based on the enum values.
2. Conversion and Parsing
Conversion and parsing involve transforming enum values between their underlying types and string representations or other types, as needed.
package main
import (
"fmt"
"strconv"
)
// Declaring an enum type
type Color int
// Enumerating colors
const (
Red Color = iota
Green
Blue
)
func (c Color) String() string {
return [...]string{"Red", "Green", "Blue"}[c]
}
func main() {
// Converting enum to string
colorStr := Red.String()
fmt.Println(colorStr)
// Parsing string to get enum value
val, _ := strconv.Atoi("1")
color := Color(val)
fmt.Println(color)
}
In this golang enum example, a Color
enum is converted to a string representation through a method, and a string is parsed to obtain the corresponding enum value. This enhances the enums’ usability, allowing them to be used more flexibly across different parts of an application where various representations may be required.
Enums and Structs
Embedding enums within structs is a powerful way to leverage type safety and expressive code within your Golang applications. By using enums in struct fields, you can encapsulate specific predefined values within your data structures, making your code more robust and understandable. Below are ways to implement and utilize golang enums within structs:
1. Embedding Enums in Structs
You can embed enum types directly within your structs to define the permissible values for specific fields, as shown in the example:
package main
import "fmt"
// Declaring an enum type for departments
type Department int
// Enumerating departments
const (
HR Department = iota
Finance
Marketing
IT
)
// Declaring a struct Employee
type Employee struct {
Name string
Department Department
}
func main() {
// Initializing a struct with an enum value
emp := Employee{
Name: "John Doe",
Department: IT,
}
fmt.Println("Employee:", emp.Name, "Department:", emp.Department)
}
In this golang enum example, an Employee
struct embeds the Department
enum, enhancing the struct’s expressiveness and type safety.
2. Using Enums for Struct Field Types
Enums can also be used as types for struct fields, emphasizing the field’s specific nature and permissible values:
package main
import "fmt"
// Declaring an enum type for statuses
type Status int
// Enumerating statuses
const (
Active Status = iota
Inactive
Resigned
)
// Declaring a struct User
type User struct {
Username string
Status Status
}
func main() {
// Initializing a struct with an enum type field
user := User{
Username: "jane_doe",
Status: Active,
}
fmt.Printf("Username: %s, Status: %d\n", user.Username, user.Status)
}
This code illustrates a User
struct where the Status
field uses a golang enum, which makes the status’s permissible values explicit and the code more expressive and maintainable.
Enums and JSON
Working with JSON is a common requirement in Golang applications, and integrating enums into this process necessitates a clear understanding of how to marshal and unmarshal enums effectively. Additionally, handling unknown enum values gracefully is crucial to maintain the robustness of the application. Below are the practices and examples that elaborate on these aspects of working with a Golang enum in a JSON context.
1. Marshalling and Unmarshalling Enums
Marshalling and unmarshalling enums involve converting enums between their Golang representations and JSON string values.
package main
import (
"encoding/json"
"fmt"
)
// Declaring an enum type for roles
type Role int
// Enumerating roles
const (
Admin Role = iota
User
Guest
)
// Implementing String() method for the Role enum
func (r Role) String() string {
return [...]string{"Admin", "User", "Guest"}[r]
}
// UnmarshalJSON custom implementation
func (r *Role) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
switch s {
case "Admin":
*r = Admin
case "User":
*r = User
case "Guest":
*r = Guest
default:
return fmt.Errorf("invalid role")
}
return nil
}
// MarshalJSON custom implementation
func (r Role) MarshalJSON() ([]byte, error) {
return json.Marshal(r.String())
}
func main() {
// Marshalling an enum value
role := Admin
data, err := json.Marshal(role)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data))
// Unmarshalling JSON into an enum value
jsonData := `"User"`
var unmarshalledRole Role
err = json.Unmarshal([]byte(jsonData), &unmarshalledRole)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(unmarshalledRole)
}
This example showcases a Golang enum (Role
) with custom marshalling and unmarshalling methods to handle JSON conversion seamlessly.
2. Handling Unknown Enum Values
Handling unknown or unexpected enum values is essential to ensure that your application can fail gracefully and handle errors effectively.
package main
import (
"encoding/json"
"fmt"
)
// Previous Role enum and methods here...
func main() {
// Unmarshalling JSON with an unknown enum value
jsonData := `"SuperUser"`
var unmarshalledRole Role
err := json.Unmarshal([]byte(jsonData), &unmarshalledRole)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Role:", unmarshalledRole)
}
In this part of the code, when an unknown enum value ("SuperUser") is encountered during unmarshalling, a custom error is returned, allowing for graceful error handling and maintaining the application’s robustness while working with JSON and Golang enums.
Error Handling with Enums
Enums can be leveraged effectively for error handling in Golang. By representing errors as enums, you can create a standardized set of errors that can be reused across your application, making your error handling more consistent and expressive. Custom error messages can also be attached to these enums to provide clearer error descriptions. Here’s how you can achieve this with golang enums:
1. Using Enums to Represent Errors
You can use enums to represent various error states in your application, promoting consistency and reusability of error handling code.
package main
import "fmt"
// Declaring an enum type for errors
type AppError int
// Enumerating error codes
const (
NotFoundError AppError = iota
UnauthorizedError
InternalError
)
// Implementing the Error() method for the AppError enum
func (e AppError) Error() string {
return [...]string{"Not Found", "Unauthorized", "Internal Error"}[e]
}
func main() {
// Returning an enum error
err := UnauthorizedError
if err != nil {
fmt.Println("Error:", err.Error())
}
}
In this example, the AppError
golang enum represents various error states, and an Error()
method is implemented to make the enum satisfy the error interface.
2. Custom Error Messages
Custom error messages can be integrated with enum errors to provide more context and clearer error descriptions.
package main
import "fmt"
// Enumerating error codes with custom messages
const (
NotFoundErrorMsg = "The requested resource could not be found."
UnauthorizedErrorMsg = "You are not authorized to access this resource."
InternalErrorMsg = "An internal error occurred. Please try again later."
)
// Using the enum to return a custom error message
func getErrorMessage(err AppError) string {
switch err {
case NotFoundError:
return NotFoundErrorMsg
case UnauthorizedError:
return UnauthorizedErrorMsg
case InternalError:
return InternalErrorMsg
default:
return "An unknown error occurred."
}
}
func main() {
// Getting a custom error message from an enum error
err := NotFoundError
fmt.Println("Error:", getErrorMessage(err))
}
Here, custom error messages are associated with each golang enum error code, and a function is used to retrieve these messages based on the error enums, providing a more detailed and context-rich error description.
Best Practices - When to Use Enums
Enums in Golang, while not explicitly supported as a native type, are powerful tools for making your code more expressive, readable, and less prone to errors. Understanding when to use enums is key to leveraging their benefits effectively. Below are some scenarios and examples where using a golang enum is advantageous:
1. Representing a Set of Predefined Values
Enums are useful when a variable should only take on a specific set of values.
package main
import "fmt"
// Declaring an enum type for days
type Weekday int
// Enumerating weekdays
const (
Monday Weekday = iota
Tuesday
Wednesday
Thursday
Friday
)
func main() {
today := Wednesday
fmt.Println("Today is", today)
}
In this golang enum example, days of the week are represented, ensuring that only valid days can be used.
2. State Representation
Enums are suitable for representing different states in a system.
package main
import "fmt"
// Declaring an enum type for states
type State int
// Enumerating states
const (
Pending State = iota
Approved
Rejected
)
func main() {
applicationState := Pending
fmt.Println("Application is currently in the", applicationState, "state.")
}
Here, a golang enum represents various states of an application, enhancing code expressiveness and reliability.
Real World Examples using Golang Enums
Understanding the application of enums in real-world scenarios provides a clearer picture of their utility. Below are practical examples illustrating how to employ golang enums effectively:
1. User Roles and Permissions:
Enums can be used to define various roles and permissions within an application.
package main
import "fmt"
// Declaring an enum type for roles
type Role int
// Enumerating roles
const (
Guest Role = iota
User
Moderator
Administrator
)
func main() {
userRole := Moderator
fmt.Println("The user has the role of a", userRole)
}
In this example, a golang enum is utilized to represent different user roles within an application, simplifying role management.
2. HTTP Status Codes:
Enums can represent various HTTP status codes, making the code more readable.
package main
import "fmt"
// Declaring an enum type for HTTP status codes
type HTTPStatus int
// Enumerating some HTTP status codes
const (
OK HTTPStatus = 200
BadRequest = 400
NotFound = 404
)
func main() {
status := NotFound
fmt.Println("HTTP Status Code:", status)
}
Using a golang enum to symbolize HTTP status codes enhances the readability and expressiveness of handling HTTP responses in the code.
Popular Enum Libraries and Tools
Several libraries and tools can facilitate working with enums in Golang. These libraries often provide functionalities like string conversion, JSON marshalling/unmarshalling, and more, simplifying the process of working with enums. Here’s a look at some popular libraries and tools that you can use to enhance your work with golang enums:
1. go-enum
go-enum
is a popular tool that generates enum-like structures for Golang. It provides a plethora of features like JSON and SQL interfaces out of the box, which can be handy while dealing with databases and JSON APIs.
go get -u github.com/abice/go-enum
Example of defining a golang enum using go-enum
:
//go:generate go-enum -f=$GOFILE --marshal
package main
// ENUM(
// Pending
// Approved
// Rejected
// )
type Status int
Running go generate
will produce methods for the enum, like String()
, MarshalJSON()
, and UnmarshalJSON()
.
2. golang/protobuf
If you are working with Protobuf in your Golang project, the golang/protobuf
library naturally supports enum types. It’s especially useful in GRPC services where protobuf is a common choice for schema definition.
go get -u google.golang.org/protobuf
Protobuf file example:
syntax = "proto3";
package main;
enum StatusCode {
UNKNOWN = 0;
OK = 1;
FAILED = 2;
}
When compiled, this protobuf file will generate a golang enum with necessary methods and constants.
3. Stringer
Stringer
is a tool by the Golang tools subrepository which creates methods for formatted string output of constants, which is useful for enums.
go get golang.org/x/tools/cmd/stringer
Using Stringer, you can create human-readable strings for your enums, facilitating easier debugging and logging.
//go:generate stringer -type=Weekday
package main
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
)
After running go generate
, Stringer will create a String()
method for the Weekday type.
Frequently Asked Questions (FAQs)
What is an Enum in Golang?
Enum, short for enumeration, is a distinct data type consisting of a set of named values called elements or members. Golang doesn’t have a native enum type, but enums can be created using constant iota identifiers. Iota generates incrementing numbers to represent enum values within a const
block.
How do you define an Enum in Golang?
Enums can be defined using constant (const
) values incremented by iota. For instance, you can define an enum for days of the week like this: type Weekday int; const (Sunday Weekday = iota, Monday, Tuesday)
where each day is automatically assigned an incremental integer starting from zero.
How does Iota work in Golang Enums?
Iota is a predeclared identifier that represents the untyped integer ordinal number of a constant specification in a const
declaration. When used in enums, it simplifies the definition by automatically assigning incremental values to each constant, starting from zero.
Can Enums in Golang be Marshalled into JSON?
Yes, enums in Golang can be marshalled into JSON. However, by default, they’ll be treated as integers. Custom methods like MarshalJSON()
and UnmarshalJSON()
can be implemented for the enum type to control the marshalling and unmarshalling process, allowing enums to be represented as strings (or any other format) in JSON.
How can you convert strings to Enums in Golang and vice versa?
For converting strings to enums and vice versa, you can create mapping functions. For converting an enum to a string, you might use a switch statement or an array of strings. To convert a string back to an enum, you could use a map with strings as keys and enum values as values, or use a switch statement to map strings back to corresponding enum values.
Can you use strings directly as Enum values in Golang?
Not directly. Enums in Golang are typically declared as sets of unique integer constants, often with iota. However, you can map strings to enums and vice versa by creating custom methods or look-up maps, allowing you to work with strings as if they were enum values in practice.
Is there a native Enum type in Golang?
No, Golang doesn’t have a native enum type. Enums in Golang are customarily created using constant iota identifiers, which simplify the declaration and management of sets of unique constants, typically to represent categorical values like states, statuses, or types.
How do you iterate over an Enum in Golang?
Since Golang enums are essentially integers, they don’t support direct iteration. However, you can define slices or arrays of enum values and iterate over those, or you could define a range of valid enum values and iterate over the integers within that range, converting each integer to the corresponding enum value.
Summary
Golang enums are a powerful construct that allow for the creation of a set of named integer constants, aiding in the development of more expressive and understandable code. Though Golang doesn't support enums as a native type, they are conventionally created using constant blocks with the iota enumerator, which automatically assigns incrementing values to constants, effectively simulating enum behavior. Enums enhance code robustness by allowing variables to take values from a predefined set, thus reducing the risk of invalid values. Additionally, various libraries and tools, such as go-enum
and Stringer
, further simplify working with enums in Golang, adding functionalities like string conversion and JSON marshalling.
For a deeper understanding and reference on working with constants and iota for creating enums in Golang, you can refer to the official documentation: Go by Example: Constants and Go Documentation: Iota.
In the example code for “Creating enums by multiplying” there is a line ” East // 2 * 1000 = 4000″.
I’m new to `go`, but this looks… odd.
-E
Thanks for highlighting the typo. In the next line the output was correct but in the comments 4000 was wrongly written instead of 2000.