Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get all constants of a type in Go

Tags:

go

fsm

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.

like image 934
IamLearning Avatar asked Aug 25 '17 20:08

IamLearning


3 Answers

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.

like image 176
Ainar-G Avatar answered Oct 21 '22 08:10

Ainar-G


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.

like image 34
captncraig Avatar answered Oct 21 '22 09:10

captncraig


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())
                }
            }
        }
    }
}
like image 2
Stephan Lukits Avatar answered Oct 21 '22 09:10

Stephan Lukits