Golang flag redefined
Asked Answered
A

3

22

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?

Ader answered 9/3, 2018 at 12:6 Comment(3)
golang.org/pkg/flag/#FlagSet. The exported functions you're calling are using this FlagSet underneath. Use NewFlagSet to have as many sets as you'd like.Aurelia
@Aurelia Unfortunately FlagSet can't be used to solve it, see my answer for a counter-example.Phrenic
@Phrenic thanks, that's a great answer and counter-example.Aurelia
P
17

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.

Phrenic answered 9/3, 2018 at 13:47 Comment(3)
As to why I need to register a flag twice by the same name, I have reusable service components that may contain the same flag. A service may contain zero or more components with the same flag, thus individual components that require specific flags don't know if those flags have already been registered by another component. Your suggestion to have a global container for the flags is a good alternative, but was hoping to avoid that since the flags package is already basically doing just that.Viscose
Would it be a good idea to move flag.Parse() at the end of the init() function ?Lanlana
@PierreLouis To what end?Phrenic
A
1

I have a function that i run 2 times in unit-test. When i run my tests i get 'flag redefined' error. To solve this problem i used flag.Lookup function and I checked if the flags are defined.

        func myFunc() {
            var firstParam = new(string)
            var secondParam = new(string)
            if flag.Lookup("param1") == nil {
                firstParam = flag.String("param1", "", "first param")
            }
           if flag.Lookup("param2") == nil {
                secondParam = flag.String("param2", "", "second param")
            }
          flag.Parse()
       }
Azine answered 9/5, 2023 at 21:40 Comment(0)
C
0

base on @icza 's answer I add a demo usage.

old declear

var flg_debug = flag.Bool("debug", false, "debug level log")

new declear

NOTE: redefined flag should use same type

var flg_debug = flag_bool("debug", false, "debug level log")

func flag_bool(name string, value bool, usage string) *bool {
    if flag.Lookup(name) == nil {
        return flag.Bool(name, value, usage)
    } else {
        val := flag.Lookup(name).Value.(flag.Getter).Get().(bool)
        return &val
    }
}
Cosset answered 29/1 at 18:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.