How to get all constants of a type in Go
Asked Answered
D

4

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.

Degraw answered 25/8, 2017 at 20:3 Comment(7)
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" ?Ensue
The only thing that comes to mind is to keep them up to date in a map[string]interface{} or maybe a []interface{}.Transistorize
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 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.Transistorize
@Degraw 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.Skip
@Transistorize 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.Knack
@Knack 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.Transistorize
This could be a duplicate of https://mcmap.net/q/53897/-what-is-an-idiomatic-way-of-representing-enums-in-go/121660Ensue
E
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.

Eous answered 25/8, 2017 at 20:9 Comment(6)
I am going back and forth in my mind between thinking that's really really cool, and thinking it is really really dirty.Ensue
And it only works for iota. Doesn't help in the general case where constants could be anything.Transistorize
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.Knack
@Knack 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.Ensue
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.Degraw
As unsettling as this answer is at first glance, something similar is used in crypto 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.Gigantism
E
9

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.

Ensue answered 25/8, 2017 at 20:9 Comment(3)
It's actually doable at runtime with go/ast and co. Although probably not sensible in this case.Skip
@mkopriva, I am not sure how go/ast helps in situations where you don't have the source anymore.Ensue
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.Skip
C
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())
                }
            }
        }
    }
}
Cristobalcristobalite answered 5/2, 2022 at 16:18 Comment(0)
S
-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
}

Severalty answered 26/2, 2021 at 10:47 Comment(1)
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.Achaea

© 2022 - 2024 — McMap. All rights reserved.