How to do Enums in Go
Marco Franssen /
11 min read • 2036 words
It has been a while since I wrote a blog on Go. Since I'm getting the question if Go supports enums every now and then, I thought it would be good to write an article on how to do enums
in Go.
Go natively does NOT have an enum
type like you might be used to from c#
or Java
. However that doesn't mean we can easily define our own type.
In this blog we will cover defining our own type, combined with a piece of code generation. If you are new to Go
, then consider reading Start on your first Go project first.
Enums make up a nice API experience for consumers of your library by adding some type safety and giving the developer consuming the library some guidance on the available values.
Now we could use strings
all over the place to pass our values, however using an enum is also more efficient as we will be using an integer
to express and store these values in memory. You might be thinking… How can an integer be more convenient for the users of my library? Well, they aren't so let me show you how to get that Enum like developer experience in Go.
Imagine we are building a simple car configuration tool that allows to define the brand and color of a car. To offer a nice API for the consumer of our car
package I want to offer them some predefined values (Enums) from which they can pick a brand and color. For the car model I go with a free format string as there are too many variations.
First lets have a look on the code I would like to write as a consumer of my car
package.
Running this program should print the following output.
By using an enum for the Brand
and Color
I get a very nice API to use my car
package as a developer. It adds type safety as well it offers a predefined list of choices for brands and colors.
Now let's have a look on how we can define the struct and constructor function in our car
package first.
You notice that in this constructor I have used some custom types for Brand
and Color
. Lets have a look on how we can define these custom types and give them an enum like behavior.
What we did here is defining a custom type Brand
that is stored in memory as an integer. Then we define some predefined values as constants using iota
. iota
adds auto incremented values for a group of constants. Read more on iota
here.
When compiling this code this will in practice result in the following values being assigned to the constant fields we defined.
| Field | Value | | ---------- | ----: | | BMW | 0 | | Mercedes | 1 | | Audi | 2 | | Toyota | 3 | | Volkswagen | 4 | | Porsche | 5 | | Ferrari | 6 |
Now you can do the same to define a Color
type. Please go ahead and try that on your own. Ensure you define at least the colors Gray
and Red
to continue this tutorial.
Now if I would run my code we will notice it doesn't print that nicely as we have shown before.
The reason for this ugly output is that our Car
type does NOT implement the Stringer interface. We are using %s
with our fmt.Printf
invocation to print our type as a String, therefore we need to implement the stringer
interface to define how our Car
prints as a string
.
Interfaces in Go are implicit which means we don't need to define which interface we implement. We only need to add the function from the Stringer interface on our type.
To know more about interfaces in Go check out my other blog on interface and type assertions. Now lets add this function to our
Car
type.
Although this prints already slightly better we still don't get the desired output for our Brand
and Color
types.
Guess what?! We also need to implement the Stringer interface for our Brand
and Color
types. However this time I don't want to manually implement this. I want to generate the implementation so I can easily update it when we are adding new constant values for theses types in the future.
To generate the implementation we will use a tool called stringer
. We can install this tool using the following command.
Now in code we can add a //go:generate stringer -type=Brand
statement to generate the String
method on our type. I prefer to add this as a comment in front of my type definition, but in practice this can be anywhere in your package.
//go:generate
is a special type of comment that that will be executed when we usego generate
.
Same we will do for our Color
type.
With the tool and the comment in place we can now simply run go generate ./...
. This will create a new file called car/brand_string.go
and a new file called car/color_string.go
. Go ahead and have a look at the generated code. Now every time you changed your constants, e.g. added new values changed order or removed values, simply run go generate ./...
again.
If we run our program now, we finally get the desired output.
Although this is already pretty neat, I would like to add one more thing to be able to marshal and unmarshal our values to and from JSON as a string
. Lets start with marshalling to JSON. To do so we will implement the TextMarshaler interface on our Brand
type.
Before we do the same for our Color
type I suggest we will first try to print some JSON so we can see the difference if we don't implement this TextMarshaler interface.
Add following tags to your Car
type so it nicely uses lowercased keys for our JSON.
Lets now try to print our Car as JSON by adding a piece of code to our main function.
Our program now shows following output:
As you can see we now nicely have our Brand
printed as a string in our JSON, but the Color
is still represented as an integer in our JSON. Go ahead and implement the TextMarshaler interface for your Color
type.
Once done, you should see both brand and color as a string
in the JSON.
So what if we would like to unmarshal JSON into our struct? For that we need to implement the TextUnmarshaler interface.
Do the same for our Color
type and add some code to our main function to create another car from a JSON string.
Now before popping a :beer: lets run our application one more time to see the result.
:warning: Please note if you forget to regenerate the stringer implementation for your types you will get something like following message.
Go ahead and regenerate the code and run your application again.
Congratulations :tada:, you made it to the end of this article. Want to learn more about Golang? Consider reading my other articles on Go.
Leave me a comment down below in the comments section.
TL;DR
The layout of our project looks like this.
Before running the application we will generate {brand,color}_string.go
using the following command. We will also need to install the stringer generation tool first.
Now we can run our application.
Curious how it all works? Then read the full article.
References
- Start your first Golang project
- Go interfaces and type assertions
- Stringer interface
- TextMarshaler interface
- TextUnMarshaler interface
- Full code at Github
Please leave me a comment below.