Golang flag redefined

ElfsЯUs picture ElfsЯUs · Mar 9, 2018 · Viewed 11.7k times · Source

I have flags that may be defined in multiple components. Its not possible to know if a component has already defined a flag, as such if two components Foo and Bar require flag Zed, but Foo and Bar must define flag Zed. In this case, Go's flag's will panic with an error flag redefined.

Is there any way to optionally initialize a flag only if it hasn't been initialized elsewhere? Or alternatively check if a flag has already been set and if it has then lookup the flag value?

The only way I see to do this is with something like this:

var fooFlag *string

func init() {
    if flag.Lookup("foo") == nil {
        fooFlag = flag.String("foo", "", "some flag foo")
    }
}

func initFlags() {
    if fooFlag == nil && flag.Lookup("foo") != nil {
        temp := (*flag.Lookup("foo")).Value.(flag.Getter).Get().(string)
        fooFlag = &temp
    }
}

func main() {
    flag.Parse()
    initFlags()
    ...
}

But this is incredibly ugly (and not sure how well this will work). Any thoughts on a better way to go about this?

Answer

icza picture icza · Mar 9, 2018

Unfortunately there isn't an easier way with the standard library, because every flag may only be registered once. We'll see how we can simplify your code, and also what we can do to avoid this pattern.

Flag.FlagSet doesn't help

The usage of flag.FlagSet is not a solution, it was designed to support subcommands (see this question for an example: Defining Independent FlagSets in GoLang).

To demonstrate why it doesn't help: let's assume you have 2 flagsets (one may be the detaulf of the flag package). The first flagset may contain the "foo" and "bar" flags, and the second flagset may contain the "foo" and "baz" flags. What happens if the user provides values for all 3? The call to FlagSet.Parse() of the first flagset will report an error:

flag provided but not defined: -baz

And similarly the FlagSet.Parse() of the second flagset would also report error for undefined -bar.

Simplifying your code

If you want to keep your approach, note that you can simplify your code by using flag vars (flag.StringVar()), and then in initFlags() you can simply get a string value and assign to your flag variable like this:

var foo string

func init() {
    if flag.Lookup("foo") == nil {
        flag.StringVar(&foo, "foo", "", "this is foo")
    }
}

func initFlags() {
    // "foo" does exist: if noone else registered it, then we did
    foo = flag.Lookup("foo").Value.(flag.Getter).Get().(string)
}

func main() {
    flag.Parse()
    initFlags()
    ...
}

Also if you want to handle multiple flags like this, then you should create helper functions to not repeat the (now less "ugly") code:

func regStringVar(p *string, name string, value string, usage string) {
    if flag.Lookup(name) == nil {
        flag.StringVar(p, name, value, usage)
    }
}

func getStringFlag(name string) string {
    return flag.Lookup(name).Value.(flag.Getter).Get().(string)
}

And using the above helpers, registering 3 flag variables is like:

var foo, bar, baz string

func init() {
    regStringVar(&foo, "foo", "", "this is foo")
    regStringVar(&bar, "bar", "", "this is bar")
    regStringVar(&baz, "baz", "", "this is baz")
}

func initFlags() {
    foo = getStringFlag("foo")
    bar = getStringFlag("bar")
    baz = getStringFlag("baz")
}

func main() {
    flag.Parse()
    initFlags()
    ...
}

Solution?

The obvious solution would be to use different names. If common names may collide, you may prefix them e.g. with the package name or something. But you should use different, distinct, unique names.

Think about it. If you want to use the same flag (same by name), why are you attempting to register it twice, at 2 different places?

If you do want to use the same flag from multiple places, then "outsource" the flag variable and its registration. E.g. create a package that will take care of this, and from both places where it's needed, refer to this package. Or make sure to distribute the values of the flag variable to places where it's needed.