Question

How would I go about having a package register some object (for instance a function) to a registry at load time such that adding a new package to the program will automatically add new functionality to the program without having to modify code in other packages?

Here's a code sample which should illustrate what I'm trying to do.

src/say/say.go:

package main

import (
    "os"
    "reg"
)

func main() {
    if len(os.Args) != 2 {
        os.Stderr.WriteString("usage:\n    say <what_to_say>\n")
        os.Exit(1)
    }

    cmd, ok := reg.GetFunc(os.Args[1])
    if ok {
        os.Stdout.WriteString(cmd())
        os.Stdout.Write([]byte{'\n'})
    } else {
        os.Stderr.WriteString("I can't say that!\n")
        os.Exit(1)
    }
}

src/reg/reg.go:

package reg

var registry = make(map[string]func() string)

func Register(name string, f func() string) {
    registry[name] = f
}

func GetFunc(name string) (func() string, bool) {
    f, ok := registry[name]
    return f, ok
}

src/hi/hi.go:

package hi

import (
    "reg"
}

func init() {
    reg.Register("hi", func() string {
        return "Hello there!"
    })
}

When coding this up, I naively supposed that perhaps the package "hi" would be found by the go compiler and compiled into the binary. Then, at load time, the init() function would run. If that was how things worked, I'd have been able to drop in something like the following to add a new "say no" command:

src/no/no.go:

package no

import (
    "reg"
)

func init() {
    reg.Register("no", func() string {
        return "Not a chance, bub."
    })
}

But, it doesn't seem to work that way.

I may just be thinking about the problem too much through a Pythonic lens, but is there some way to accomplish something somewhat like what I'm shooting for? If not, I'll change my tack and I will have learned something new about the Go way of doing things.

Thanks in advance!

Was it helpful?

Solution

Since you must use import in order for the compiler add a package, my suggestion would be to do the following:

Instead of using multiple drop-in packages, you could have only one single package with multiple drop-in files. Each command file is placed in the same package folder (cmds). This is possible since you are allowed to have multiple init in a package, and you would not have to make any edits to say.go, no matter how many new drop-in files you add.

package main

import (
    "os"
    "reg"
    _ "cmds"
)
....

And previous package no

// Command no
package cmds

import (
    "reg"
)

func init() {
    reg.Register("no", func() string {
        return "Not a chance, bub."
    })
}

OTHER TIPS

Based on what I read about the init function, I think your example would work if you just added "hi" and "no" to the list of packages you are importing in say.go. Does it work if you do that?

I know you did not want to change the code in say.go, so I suppose that isn't really a solution.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top