Assign a string representation to an enum/const

24,778 views
Skip to first unread message

Péter Szilágyi

unread,
Nov 28, 2013, 4:48:47 AM11/28/13
to golang-nuts
Hi all,

  This might be a bit unorthodox question, but I thought I'd ask away anyway :D Is there a straightforward possibility to assign string representations to an enumeration inline (i.e. at the point of declaring the enum), or possibly getting the original source code string back?

  E.g. I have an enumeration like:

const (
  Abc = iota
  Def
  Ghi
  Etc
)

  And during debugging or just logging when I dump my structs containing these, I'd like to have a textual representation oppose to an int (mainly because I have many tens of entries, and manual lookup is bothersome).

  Of course, I could map the ints to strings, but I'm wondering if there is something simpler, similar to tagging a struct field with a json name... i.e.

type S struct{
  Address string `json:"address"`
}

  If not, it's not really an issue, I'm just curious :)

Thanks,
  Peter

egon

unread,
Nov 28, 2013, 5:29:04 AM11/28/13
to golan...@googlegroups.com

Péter Szilágyi

unread,
Nov 28, 2013, 5:49:07 AM11/28/13
to egon, golang-nuts
Hi,

  Thanks for the variations. Appreciate it :). Alas, the first one was specifically what I wanted to avoid (it would also be simpler with a map[enum]string).

  The second version however is close to what I intended, but it does not scale to multiple enums (other than having multiple iota functions or adding a second parameter). So although workable, it adds a bit too much complexity in my opinion.

Cheers,
  Peter


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Kevin Gillette

unread,
Nov 28, 2013, 6:43:23 AM11/28/13
to golan...@googlegroups.com
http://play.golang.org/p/qyICMRW7i5

Main point is to write a test to verify that the enum representation has reasonable characteristics.

On a side note, compiled Go binaries don't bundle their source code, especially not for "compile-time only" concepts like consts (which literally don't produce symbols in the linked output).

There are more "managed" ways to accomplish enum <-> string representations, but those ways in Go sacrifice the property that the values are consts (instead you have to use a map or something equivalent). const lookups are free at runtime, but they were already resolved at compile time; map lookups are cheap, but "cheap" is a lot more expensive than "free".

bronze man

unread,
Nov 28, 2013, 6:50:13 AM11/28/13
to golan...@googlegroups.com
type Enum string

const (
EnumAbc Enum = "abc"
EnumDef Enum = "def"
)
This type work fine with json.

egon

unread,
Nov 28, 2013, 7:52:33 AM11/28/13
to golan...@googlegroups.com, egon


On Thursday, November 28, 2013 12:49:07 PM UTC+2, Péter Szilágyi wrote:
Hi,

  Thanks for the variations. Appreciate it :). Alas, the first one was specifically what I wanted to avoid (it would also be simpler with a map[enum]string).
 
  The second version however is close to what I intended, but it does not scale to multiple enums (other than having multiple iota functions or adding a second parameter). So although workable, it adds a bit too much complexity in my opinion.


You may also do... http://play.golang.org/p/8o5WddNQVP :D, but that is even worse... :P

+ egon

Rodrigo Kochenburger

unread,
Nov 28, 2013, 1:22:20 PM11/28/13
to bronze man, golan...@googlegroups.com
I'm gonna second what bronze said. Create a string type and define your constants with that type, but I'd create a more specific meaningful type rather than a generic Enum.

For example:


type UserType string

const (
    Admin          UserType = "administrator"
    RegularUser                 = "regular user"
    .....
)

Note that you only need to define the type for the first const definition in the block.

Hope it helps.

- RK


--

Matt Harden

unread,
Nov 28, 2013, 8:26:52 PM11/28/13
to Rodrigo Kochenburger, bronze man, golan...@googlegroups.com
One could also bastardize types as a substitute for symbols, and use reflection to look up the names of the types.

minux

unread,
Dec 2, 2013, 12:43:38 AM12/2/13
to Péter Szilágyi, golang-nuts
Actually, I'm wondering if we have discussed tags for constants, which could solve the problem nicely.
for example:
const (
     Abc = iota `Abc`
     Def        `Def`
     Ehg        `Ehg`
)

 

Kevin Gillette

unread,
Dec 2, 2013, 2:07:13 AM12/2/13
to golan...@googlegroups.com, Péter Szilágyi
On Sunday, December 1, 2013 10:43:38 PM UTC-7, minux wrote:
Actually, I'm wondering if we have discussed tags for constants, which could solve the problem nicely.
for example:
const (
     Abc = iota `Abc`
     Def        `Def`
     Ehg        `Ehg`
)

Afaik, right now tags (and reflection) are a strictly runtime concept, while constants are a strictly compile-time concept. Especially given that in your example, these are untyped constants, there's really no way to reflect on Abc in the way that you suggest without modifying the compiler to resolve constant reflect expressions. On the other hand, if you mean that tagged constants would not be inspectable, but would rather just be used to make the compiler understand that the precise expression `string(Abc)` statically resolves to "Abc", with the caveat that `func(v int) string { return string(v) }(Abc) }` will resolve to "\x00".

minux

unread,
Dec 2, 2013, 2:17:56 AM12/2/13
to Kevin Gillette, golang-nuts, Péter Szilágyi
the string(Abc) will be too magical.

I should have set a type for the constants in my example. with this proposal, types with tagged constants won't
be compile time concept anymore. We can use reflect on them.

In fact, we can have two kinds of constants, light-weight one like the one we have now, and heavy-weight one
that has tags and could be queried for tags (or even name) given its value on runtime by reflect.

i'm worried about that the heavy-weight one is a little too heavy for Go.

Kevin Gillette

unread,
Dec 2, 2013, 3:20:35 AM12/2/13
to golan...@googlegroups.com, Kevin Gillette, Péter Szilágyi
On Monday, December 2, 2013 12:17:56 AM UTC-7, minux wrote:
I should have set a type for the constants in my example. with this proposal, types with tagged constants won't
be compile time concept anymore. We can use reflect on them.

What would happen with the following?

type T int
const (
   X T = 0 `X`
   Y T = iota `Y`
   Z `Z`
)
func (t T) String() string { /* reflective implementation here */ }
func main() { fmt.Println(Y) }

Jesse van den Kieboom

unread,
Dec 2, 2013, 4:07:30 AM12/2/13
to golan...@googlegroups.com, Kevin Gillette, Péter Szilágyi
It doesn't really seem worth it. Having written some simple enum -> string (big switch) functions, it's pretty easy and quick to do by hand for reasonably sized enums. The downsides are I guess that it's easy to get out of sync (adding new enum values and forgetting to update the enum -> string function) and doing it for very large enums is tedious. Of course, it would be pretty trivial to write a small go tool to generate these functions.

Johann Höchtl

unread,
Dec 2, 2013, 7:59:48 AM12/2/13
to golan...@googlegroups.com


Am Donnerstag, 28. November 2013 10:48:47 UTC+1 schrieb Péter Szilágyi:
Hi all,

  This might be a bit unorthodox question, but I thought I'd ask away anyway :D Is there a straightforward possibility to assign string representations to an enumeration inline (i.e. at the point of declaring the enum), or possibly getting the original source code string back?

Actually, I wouldn't stop here. What makes tags on structs any special than having tags on enums, function declarations etc? That would enable some powerful reflection-based features like declarative security control.

func (db* db) DeleteResource(res *Resource) `roles:`Admin, PowerUser` {
   // Some text here
}

Johann

roger peppe

unread,
Dec 2, 2013, 8:23:15 AM12/2/13
to Rodrigo Kochenburger, bronze man, golan...@googlegroups.com
On 28 November 2013 18:22, Rodrigo Kochenburger <div...@gmail.com> wrote:
> I'm gonna second what bronze said. Create a string type and define your
> constants with that type, but I'd create a more specific meaningful type
> rather than a generic Enum.
>
> For example:
>
>
> type UserType string
>
> const (
> Admin UserType = "administrator"
> RegularUser = "regular user"
> .....
> )
>
> Note that you only need to define the type for the first const definition in
> the block.

That's not actually true.
http://play.golang.org/p/j8kQVePG3j

The type is only propagated when the constant value is omitted.

Defining constants as strings works OK, but might not
be great for performance reasons.

I usually (manually) create a table of string values and a String method:

http://play.golang.org/p/0YzAZ2NCk4

I've considered writing something to automatically generate this
code, which would be easy enough in the simple case,
but I always get bogged down in considering other cases
(e.g. bitmaps) so I haven't actually done it yet.

egon

unread,
Dec 2, 2013, 1:34:55 PM12/2/13
to golan...@googlegroups.com, Rodrigo Kochenburger, bronze man
Something like that: http://play.golang.org/p/3QyzW92PjT :)

+ egon 

minux

unread,
Dec 2, 2013, 2:55:05 PM12/2/13
to Kevin Gillette, golang-nuts, Péter Szilágyi
The T is a stringer, so the usual thing will happen, the String() method will be invoked
and its result printed.
(just to re-iterate, I don't propose any new conversions like string(Y))

This proposal is not backward incompatible, because:
1. "heavy-weight const" uses new syntax that is not previously valid;
2. nothing will happen automagically, you will need to call new reflect method to get the
tag (or even name) for the constant.
(however, an advanced compiler might be able to inline the reflect method call with a
big switch; but this is transparent to the user)

minux

unread,
Dec 2, 2013, 2:57:32 PM12/2/13
to Jesse van den Kieboom, golang-nuts, Kevin Gillette, Péter Szilágyi
On Mon, Dec 2, 2013 at 4:07 AM, Jesse van den Kieboom <jess...@gmail.com> wrote:
It doesn't really seem worth it. Having written some simple enum -> string (big switch) functions, it's pretty easy and quick to do by hand for reasonably sized enums. The downsides are I guess that it's easy to get out of sync (adding new enum values and forgetting to update the enum -> string function) and doing it for very large enums is tedious. Of course, it would be pretty trivial to write a small go tool to generate these functions.
The Go authors seem to not be fond of arbitrary automatically generated source at build time.

This motivation for this proposal is just to solve the easily out-of-sync problem.

minux

unread,
Dec 2, 2013, 3:01:31 PM12/2/13
to Johann Höchtl, golang-nuts
the problem here is that we can't add the roles concept to the compiler, so for the
constrain to be obeyed, you need to invoke the method through special wrapper
that will check the roles. Also, I'm worried about the potential overhead for such
security check wrappers.

Kevin Gillette

unread,
Dec 2, 2013, 7:58:01 PM12/2/13
to golan...@googlegroups.com, Kevin Gillette, Péter Szilágyi
On Monday, December 2, 2013 12:55:05 PM UTC-7, minux wrote:

On Mon, Dec 2, 2013 at 3:20 AM, Kevin Gillette <extempor...@gmail.com> wrote:
What would happen with the following?

type T int
const (
   X T = 0 `X`
   Y T = iota `Y`
   Z `Z`
)
func (t T) String() string { /* reflective implementation here */ }
func main() { fmt.Println(Y) }
The T is a stringer, so the usual thing will happen, the String() method will be invoked
and its result printed.

What I meant was: what is the value that you propose would be printed, since in the presented scenario, X and Y both equal to zero. This situation isn't at all uncommon. 

minux

unread,
Dec 2, 2013, 8:16:11 PM12/2/13
to Kevin Gillette, golang-nuts, Péter Szilágyi
There should be a compile error if the const with the same value is given different tags.
(similarly, you can't write two case clauses with two consts with the same value, so you
can't give different names to them in the big switch approach, http://play.golang.org/p/DUm58Vn1U5)

PS: in your example, X == 0, whereas Y == 1, see http://play.golang.org/p/bWHLXF8iyx

ant...@indb.uk

unread,
Aug 23, 2014, 10:39:14 AM8/23/14
to golan...@googlegroups.com
I've found this on the glog package which looks quite nice to me. 

const (
infoLog severity = iota
warningLog
errorLog
fatalLog
numSeverity = 4
)

var severityName = []string{
infoLog: "INFO",
warningLog: "WARNING",
errorLog: "ERROR",
fatalLog: "FATAL",
}

sari...@gmail.com

unread,
Dec 31, 2015, 2:25:14 PM12/31/15
to golang-nuts
Many good ideas here, learnt something :). I've decided to go with this 

package main

import (
"fmt"
"reflect"
)

type CountryType struct {
Asia int32
America int32
Europe int32
}

func (c *CountryType) GetName(v int) string {
return CountryReflectType.Field(v).Name
}

var CountryEnum = CountryType{0,1,2}
var CountryReflectType = reflect.TypeOf(CountryEnum)

func main() {
fmt.Println(CountryEnum.GetName(1));
fmt.Println(CountryEnum.Europe);
}

I wanted a solution where i don't need to write it twice, once as enum, another as string, which some of the solutions here does. I'm curious how all of them performs, i'll try to benchmark them later :)

Mike Atlas

unread,
Feb 11, 2016, 1:12:41 PM2/11/16
to golang-nuts, sari...@gmail.com
How's the performance of using Reflect to obtain the string name of your integer iota enums?

yashro...@gmail.com

unread,
Oct 25, 2016, 8:29:30 AM10/25/16
to golang-nuts, bronz...@gmail.com
I tried this thing but it doesn't work. In my example the type for Count is displayed as AggregationFuncEnum, but for Sum it is only string.

Tong Sun

unread,
Apr 25, 2017, 3:24:47 PM4/25/17
to golang-nuts
Hi, 

I've convert Egon's idea into a package, https://github.com/suntong/enum. It works fine for a single enum case, see the demo in https://github.com/suntong/enum

However, now I need to define different series of enums, and I'm wondering what the best way to do it. I tried to give the package different names, but that doesn't help (the new enum serie Weekday doesn't re-start from zero):


package main


import (
 
"fmt"



 
enum "github.com/suntong/enum"
 enum2
"github.com/suntong/enum"
)


var (
 
Alpha = enum.Ciota("Alpha")
 
Beta  = enum.Ciota("Beta")


 
Sunday = enum2.Ciota("Sunday")
 
Monday = enum2.Ciota("Monday")
)


type
Example struct {
 
enum.Enum
}


type
Weekday struct {
 enum2
.Enum
}


func main
() {
 fmt
.Printf("%+v\n", Example{Alpha})
 fmt
.Printf("%+v\n", Example{Beta})
 fmt
.Println("=======")
 fmt
.Printf("%d\t%d\n", Alpha, Alpha+1)
 fmt
.Printf("%+v\t%+v\n", Example{Beta - 1}, Example{Alpha + 1})
 fmt
.Println("=======")
 
if a, ok := enum.Get("Alpha"); ok {
 fmt
.Printf("%d\n", a)
 
}
 
if b, ok := enum.Get("Beta"); ok {
 fmt
.Printf("%d: %+v\n", b, Example{b})
 
}
 fmt
.Printf("%d:%+v\n", Sunday, Weekday{Sunday})
}





The output is, 

Alpha
Beta
=======
0       1
Alpha   Beta
=======
0
1: Beta
2:Sunday

thanks

Egon

unread,
Apr 25, 2017, 3:42:23 PM4/25/17
to golang-nuts
I think the extra "enum" package would reduce readability. The code you are putting into package is ~10 lines of code... so the extra package doesn't reduce much typing, but it also loses enum typing... Depending on the enum, you may want to have different properties as well...

I chose the name "ciota" because of "custom-iota", in practice you would want something more descriptive such as "NewWeekday". 

So the solution is easy, don't create a general purpose package for it :D

+ Egon

Tong Sun

unread,
Apr 25, 2017, 4:25:14 PM4/25/17
to Egon, golang-nuts

On Tue, Apr 25, 2017 at 3:42 PM, Egon wrote:

I think the extra "enum" package would reduce readability. The code you are putting into package is ~10 lines of code... so the extra package doesn't reduce much typing, but it also loses enum typing... Depending on the enum, you may want to have different properties as well...
I chose the name "ciota" because of "custom-iota", in practice you would want something more descriptive such as "NewWeekday". 

So the solution is easy, don't create a general purpose package for it :D

OK. 

If going that route, and I have several enum series to define, I need to define a series of almost identical functions, right? Hmm..., that'd be really messy. 

Ok, I'll think of a better way on my own, which I doubt I could, :-)

Egon

unread,
Apr 25, 2017, 5:30:48 PM4/25/17
to golang-nuts, egon...@gmail.com
It depends what you need to do... I've shown in this thread multiple approaches. There probably are much more. Without knowing the context, it would be difficult to recommend a better one.

For example, you could define a DSL or custom generator or use some tool...  

+ Egon

Tong Sun

unread,
Apr 26, 2017, 2:41:41 PM4/26/17
to golang-nuts, egon...@gmail.com
Thought I need to use reflection, but after several round of wrestling, I managed to do without reflection at all:


package main


import (
 
"fmt"


 
enum "github.com/suntong/enum"
)


var (
 example
enum.Enum
 
Alpha   = example.Iota("Alpha")
 
Beta    = example.Iota("Beta")


 weekday
enum.Enum
 
Sunday  = weekday.Iota("Sunday")
 
Monday  = weekday.Iota("Monday")
)


func main
() {
 fmt
.Printf("%s\n", example.String(Alpha))
 fmt
.Printf("%s\n", example.String(Beta))

 fmt
.Println("=======")
 fmt
.Printf("%d\t%d\n", Alpha, Alpha+1)

 fmt
.Printf("%s\t%s\n", example.String(Beta-1), example.String(Alpha+1))
 fmt
.Println("=======")
 
if a, ok := example.Get("Alpha"); ok {
 fmt
.Printf("%d: %s\n", a, example.String(a))
 
}
 
if b, ok := example.Get("Beta"); ok {
 fmt
.Printf("%d: %+v\n", b, example.String(b))
 
}


 fmt
.Printf("%d:%s\n", Sunday, weekday.String(Sunday))
 fmt
.Printf("%d:%s\n", Monday, weekday.String(Monday))
}


The output:

Alpha
Beta
=======
0       1
Alpha   Beta
=======

Edward Muller

unread,
Apr 26, 2017, 3:58:28 PM4/26/17
to Tong Sun, golang-nuts, egon...@gmail.com
FWIW, I wouldn't use an "enum" (those don't exist in go) and would probably do something like this: https://play.golang.org/p/J6_hssueao


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Tong Sun

unread,
Apr 26, 2017, 4:10:11 PM4/26/17
to golang-nuts, sunto...@gmail.com, egon...@gmail.com
The problem of all code I've seen so far is that they are all demonstrate half the solution, i.e., the conversion goes one way. I know it's an easy fix, but none the less, for me it's still half solution, because I need to get and validate the enum as user input from command line, but use simple enum type internally

package main
import (
"fmt"
"github.com/mkideal/cli"
"github.com/suntong/enum"
)
var (
gender enum.Enum
male = gender.Iota("male")
female = gender.Iota("female")
theGender int = -1
)
type argT struct {
cli.Helper
Age int `cli:"age" usage:"your age"`
Gender string `cli:"g,gender" usage:"your gender (male/female)" dft:"male"`
}
// Validate implements cli.Validator interface
func (argv *argT) Validate(ctx *cli.Context) error {
if argv.Age < 0 || argv.Age > 300 {
return fmt.Errorf("age %d out of range", argv.Age)
}
ok := false
if theGender, ok = gender.Get(argv.Gender); !ok {
return fmt.Errorf("invalid gender %s", ctx.Color().Yellow(argv.Gender))
}
return nil
}
func main() {
cli.Run(new(argT), func(ctx *cli.Context) error {
ctx.JSONln(ctx.Argv())
fmt.Printf("%d:%s\n", theGender, gender.String(theGender))
return nil
})
}


$ go run 010-validatorEnum.go
{"Help":false,"Age":0,"Gender":"male"}
0:male

$ go run 010-validatorEnum.go -g male
{"Help":false,"Age":0,"Gender":"male"}
0:male

$ go run 010-validatorEnum.go -g female
{"Help":false,"Age":0,"Gender":"female"}
1:female

$ go run 010-validatorEnum.go -g unknown
ERR! invalid gender unknown

Ref: https://github.com/suntong/lang/blob/master/lang/Go/src/sys/CLI/010-validatorEnum.go

Henry

unread,
Apr 27, 2017, 2:44:25 AM4/27/17
to golang-nuts

Create a normal enum and use go generate with stringer. See https://blog.golang.org/generate 

If that doesn't work for you, you may consider writing your own function and use it with go generate. 
Reply all
Reply to author
Forward
0 new messages