在Go中使用递归引用

I want to contain all my commands in a map and map from the command to a function doing the job (just a standard dispatch table). I started with the following code:

package main

import "fmt"

func hello() {
    fmt.Print("Hello World!")
}

func list() {
    for key, _ := range whatever {
        fmt.Print(key)
    }
}

var whatever = map[string](func()) {
    "hello": hello,
    "list": list,
}

However, it fails to compile because there is a recursive reference between the function and the structure. Trying to forward-declare the function fails with an error about re-definition when it is defined, and the map is at top-level. How do you define structures like this and initialize them on top level without having to use an init() function.

I see no good explanation in the language definition.

  • The forward-reference that exists is for "external" functions and it does not compile when I try to forward-declare the function.
  • I find no way to forward-declare the variable either.

Update: I'm looking for a solution that do not require you to populate the variable explicitly when you start the program nor in an init() function. Not sure if that is possible at all, but it works in all comparable languages I know of.

Update 2: FigmentEngine suggested an approach that I gave as answer below. It can handle recursive types and also allow static initialization of the map of all commands.

Based on the suggestion by FigmentEngine above, it is actually possible to create a statically initialized array of commands. You have, however, to pre-declare a type that you pass to the functions. I give the re-written example below, since it is likely to be useful to others.

Let's call the new type Context. It can contain a circular reference as below.

type Context struct {
    commands map[string]func(Context)
}

Once that is done, it is possible to declare the array on top level like this:

var context = Context {
    commands: map[string]func(Context) {
        "hello": hello,
        "list": list,
    },
}

Note that it is perfectly OK to refer to functions defined later in the file, so we can now introduce the functions:

func hello(ctx Context) {
    fmt.Print("Hello World!")
}

func list(ctx Context) {
    for key, _ := range ctx.commands {
        fmt.Print(key)
    }
}

With that done, we can create a main function that will call each of the functions in the declared context:

func main() {
    for key, fn := range context.commands {
        fmt.Printf("Calling %q
", key)
        fn(context)
    }
}

Just populate the map inside a function before using list(). Like that.

Sry I did not see that you wrote "without init()": that is not possible.

As you might already have found, the Go specifications states (my emphasis):

if the initializer of A depends on B, A will be set after B. Dependency analysis does not depend on the actual values of the items being initialized, only on their appearance in the source. A depends on B if the value of A contains a mention of B, contains a value whose initializer mentions B, or mentions a function that mentions B, recursively. It is an error if such dependencies form a cycle.

So, no, it is not possible to do what you are trying to do. Issue 1817 mentions this problem, and Russ Cox does say that the approach in Go might occasionally be over-restrictive. But it is clear and well defined, and workarounds are available.

So, the way to go around it is still by using init(). Sorry.