如何在 Go 中获取一个类型的所有常量

How to get all constants of a type in Go

这是一个例子:

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.
}

此用例是创建有限状态机 (FSM)。能够获取所有常量将帮助我编写一个测试用例,以确保每个新值在 FSM 映射中都有对应的条目。

无法在运行时执行此操作,因为反射包不能用于它。您可以定义一个列表:

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

您可以更进一步,添加字符串表示形式或任意数量的其他内容。

您也许可以从源代码中生成这样一个列表,以便于维护,但我通常认为这样节省的时间不值得。像 stringer 这样的工具已经可以完成其中的一些工作。

如果你的常量都是有序的,你可以这样使用:

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
}

您还可以将输出缓存在例如init()。这仅在所有常量按顺序使用 iota 初始化时才有效。如果您需要适用于所有情况的东西,请使用显式切片。

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
}

既然你在谈论 test-case 我假设你有可用的类型以及定义常量的文件。我使用以下方法解决类似的问题 (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())
                }
            }
        }
    }
}