4

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.

3
  • I understand your intent, and why you want golang to make it possible. however IMHO this seems like bad practice, as list () is in a container and it knows which container it is in, infact it would only work for this specific container - I think you need some inversion of dependency. all functions when they become non-trivial will require some parameters to operate on. So kill two birds with one stone and have all functions require a context parameter, this can always include a reference to the container, and allows you to support reuse and multiple dispatchers. Commented Oct 29, 2013 at 8:21
  • Eh, it would mean that you need to define a recursive type; is that at all possible? This is not really µ-calculus, you know. Commented Oct 30, 2013 at 9:36
  • It was not a problem, so there is an example below that is implemented the way you suggested. Commented Oct 30, 2013 at 10:17

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

It confirmed my suspicion. To bad, it is something that make similar problems complicated to solve. I hope they resolve it in a future version.
1

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\n", key)
        fn(context)
    }
}

Comments

0

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.

1 Comment

That's just an alternative way of of populating it explicitly instead of putting it in the init() function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.