15

Here is an example:

package main

type State int

const (
    Created State = iota
    Modified
    Deleted
)

func main() {
    // Some code here where I need the list
    // of all available constants of this type.
}

The use case for this is to create a Finite State Machine (FSM). Being able to get all constants will help me in writing a test case that will ensure that every new value has a corresponding entry in the FSM map.

Ainar-G
  • 34,563
  • 13
  • 93
  • 119
IamLearning
  • 165
  • 1
  • 6
  • 1
    I think your terminology is a bit off. There are no classes in go. Perhaps better phrased, "get all constants in a package of a given type" ? – captncraig Aug 25 '17 at 20:05
  • The only thing that comes to mind is to keep them up to date in a `map[string]interface{}` or maybe a `[]interface{}`. – RayfenWindspear Aug 25 '17 at 20:08
  • Actually, deep down, you cannot do this at all in any way. Constants in go are untyped. The moment you use them in any way, they are given a type. Some of these suggestions will work if you are OK sacrificing their typelessness. See https://blog.golang.org/constants . I guess you are already assigning them a type `State` so this isn't a big deal, but it needs to be noted for the general case. – RayfenWindspear Aug 25 '17 at 20:17
  • @IamLearning you can aggregate constants, typed or untyped, and also evaluate constant expressions with `go/ast` and its associated packages. It's quite the job though, so unless you have an unmaintainable number of cases where you need this I recommend doing it manually as suggested in @captncraig's answer. – mkopriva Aug 25 '17 at 20:38
  • 1
    @RayfenWindspear that's not true. As the blog post you linked to indicates, there are typed constants, and the ones in the example in the question are typed (the type `State` is specified in the constant declarations). There's also no reason to put them in a `[]interface{}` - they could go in a `[]State` and remain type-safe. – Adrian Aug 28 '17 at 14:51
  • @Adrian I was speaking generally of untyped constants. I noticed later these were about a specific type. Originally I thought OP wanted to get all constants in a package, whatever the type. – RayfenWindspear Aug 28 '17 at 14:57
  • This could be a duplicate of https://stackoverflow.com/q/14426366/121660 – captncraig Aug 28 '17 at 15:03

4 Answers4

12

If your constants are all in an order, you can use this:

type T int

const (
    TA T = iota
    TB
    TC
    NumT
)

func AllTs() []T {
    ts := make([]T, NumT)
    for i := 0; i < int(NumT); i++ {
        ts[i] = T(i)
    }
    return ts
}

You can also cache the output in e.g. init(). This will only work when all constants are initialised with iota in order. If you need something that works for all cases, use an explicit slice.

Ainar-G
  • 34,563
  • 13
  • 93
  • 119
  • 2
    I am going back and forth in my mind between thinking that's really really cool, and thinking it is really really dirty. – captncraig Aug 25 '17 at 20:10
  • And it only works for `iota`. Doesn't help in the general case where constants could be anything. – RayfenWindspear Aug 25 '17 at 20:10
  • 1
    This code smells bad to me. I much prefer @captncraig's answer because it consistently uses the constants defined rather than recreating them separately. The function call also means that this slice is rebuilt every time it's called rather than once at init time. – Adrian Aug 28 '17 at 14:53
  • @Adrian an optimization here might be to cache the result of AllTs, maybe from an init function. But yeah, not very fun. Adds more code than just hardcoding some lookups. – captncraig Aug 28 '17 at 15:03
  • 1
    I agree - this works only for `iota`, but it really works well in my situation - so am gonna accept this as my correct answer. Specially love the `init()` optimization trick. – IamLearning Aug 29 '17 at 07:41
  • As unsettling as this answer is at first glance, something similar is used in crypto https://golang.org/src/crypto/crypto.go (`maxHash`) as well as at least 6 other places in the go source code. Sometimes exported from the package and other times only package visible. – skrounge Feb 04 '19 at 19:52
8

There is no way to do this at runtime, as the reflect package cannot be used for it. You could define a list:

const(
    Created State = iota
    Modified
    Deleted
)
var allStates = []State{Created, Modified, Deleted}

You may go further and add in a string representation, or any number of other things.

You may be able to generate such a list from the source to make maintenance easier, but I generally don't think that saves enough time to be worth it. There are tools like stringer that can already do some of that.

captncraig
  • 22,118
  • 17
  • 108
  • 151
  • 1
    It's actually doable at *runtime* with `go/ast` and co. Although probably not sensible in this case. – mkopriva Aug 25 '17 at 20:45
  • 2
    @mkopriva, I am not sure how go/ast helps in situations where you don't have the source anymore. – captncraig Aug 28 '17 at 15:02
  • you're right, my bad, most of the time I run Go on the machine that build the program so I'm always oblivious to the fact that the executable might be run on another machine, or the source might become unavailable. – mkopriva Aug 28 '17 at 15:14
3

Since you talking about a test-case I assume you have the type available as well as the file where the constants are defined in. I used for a similar problem the following approach (go playground):

package main

import (
    "fmt"
    "go/ast"
    "go/importer"
    "go/parser"
    "go/token"
    "go/types"
    "log"
    "strconv"
    "strings"
)

type InterestingType uint64

const const_go = `
package p

type InterestingType uint64

const (
    A InterestingType = iota << 1
    B
    C
)

type UninterestingType int

const (
    D UninterestingType = iota
    E
)
`

func main() {
    constantValues := []InterestingType{}
    ConstantsOf("InterestingType", const_go, func(v string) {
        value, err := strconv.ParseUint(v, 0, 64)
        if err != nil {
            log.Fatal(err)
        }
        constantValues = append(
            constantValues, InterestingType(value))
    })
    fmt.Printf("%#v\n", constantValues)
}

func ConstantsOf(ctype string, file string, value func(string)) {
    fset := token.NewFileSet()
    f, err := parser.ParseFile(fset, "const.go", file, 0)
    if err != nil {
        log.Fatal(err)
    }

    // Obtain type information.
    conf := types.Config{Importer: importer.Default()}
    info := &types.Info{
        Defs: make(map[*ast.Ident]types.Object),
    }
    _, err = conf.Check("p", fset, []*ast.File{f}, info)
    if err != nil {
        log.Fatal(err)
    }

    for _, d := range f.Decls {
        for _, s := range d.(*ast.GenDecl).Specs {
            v, ok := s.(*ast.ValueSpec)
            if !ok {
                continue
            }
            for _, name := range v.Names {
                c := info.ObjectOf(name).(*types.Const)
                if strings.HasSuffix(c.Type().String(), ctype) {
                    value(c.Val().ExactString())
                }
            }
        }
    }
}
Stephan Lukits
  • 306
  • 2
  • 8
-1
package main

import (
    "fmt"
)

type State int

const (
    Created State = iota
    Modified
    Deleted
)

func (s State) Name() (name string) {
    switch s {
    case Created:
        name = "created"
    case Modified:
        name = "modified"
    case Deleted:
        name = "deleted"
    }

    return
}

func main() {
    states := States()
    fmt.Println(states)
}

func States() (states []State) {
    state := State(0)

    for {
        name := state.Name()
        if name == "" {
            break
        }

        states = append(states, state)
        state++
    }
    return
}

  • If you are going to dump a code alone, you should give an example of this code results: provide an example input, run, and its output. – Brandt Feb 27 '21 at 09:26