Typescript-like enums in Golang
I’ve been learning Golang recently, and while Go’s ultra-simple nature is often refreshing it can be a bit limiting too. One Typescript feature that I found myself missing is enums. While there are a lot of people asking about this, there aren’t a lot of Go-beginner friendly explanations of how to do this out there, so I decided to dig in. After a bit of research I’m happy to report that Go can get pretty darn close!
For the uninitiated, a Typescript enum look like this:
enum Direction {
Up = 0,
Down // typescript will automatically increment the remaining items
Left, // based on the starting value (0).
Right, // so here, Right = 3
}
The above is pretty handy: now when a user of your code imports Direction
they get Up
, Down
, Left
, and Right
with it for free. Additionally, any instances of direction (e.g. Up
) have type information attached, so the compiler knows that they are instances of the Direction
type. Better yet, since Direction
is now a full-fledged type you can hang methods (e.g. asString()
off of it, allowing you to do things like Direction.asString()
later.
Getting Go to do the equivalent is a bit tricky. One common half-solution is to use constants with the iota
keyword to create separate constants for each of Up
, Down
, Left
, and Right
:
const (
Up = iota
Down // if the first value in a constant block is `iota`
Left // Go will automatically increment the rest for you.
Right // so here, Right = 3
)
The iota
keyword tells Go to automatically increment our constants. iota
returns an int
on the first call, and increments itself by 1 on every subsequent use inside a single const
block, so the above code would set Up
to 0
, and Down
to 1
, etc.
This isn’t bad, but at this point we’ve only declared a bunch of integer constants with no way to represent that Up
, Down
, Left
, and Right
are all variants of Direction
. More distressingly, we can’t attach any new methods to our Direction
type since there is no Direction
type (yet)!
One way to solve this is to combine iota
with a custom redefinition of the int
type. The key is to first declare your custom int
type, and then to set the members of your const
block to use that type instead of int
(which is what iota
will give you by default):
type Direction int
const (
Up Direction = iota
Down
Left
Right
)
This way, Up
, Down
, Left
, and Right
are all of type Direction
, but their values are still 0
, 1
, 2
, and 3
respectively. If you want to hang methods onto Direction
you can now do so by adding them to Direction
:
package main
import "fmt"
type Direction int
const (
Up Direction = iota
Down
Left
Right
)
func (dir Direction) value() int {
return int(dir)
}
func main() {
fmt.Printf("Right's value is: %d\n", Right.value())
}
This is getting pretty close to the typescript version’s usability! The only real difference is that when importing the Direction
“enum” from another file you’ll need to import the constants (Up
, Down
, Left
, and Right
) individually instead of just importing the Direction
enum itself the way you would in Typescript.
You’re also free to use any type you’d like for your custom enum type. For example, if you were working with a JSON API that represented Direction.Up
as just "up"
you could make your Direction
type a string
instead, with two minor caveats:
- You can no longer use
iota
sinceiota
won’t know how to increment a string. - You’ll need to explicitly add the
Direction
type to each line, otherwise Go will just treat it as a standard Go string.
Example:
type Direction string
const (
Up Direction = "up"
Down Direction = "down"
Left Direction = "left"
Right Direction = "right"
)
This simple approach works well for my needs, let me know in the comments if you know of a better way to get Typescript-style enums in Go!