-
Notifications
You must be signed in to change notification settings - Fork 17.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
flag: PrintDefaults panics trying to ascertain whether a flag was set #28667
Comments
Seems like this is covered in #24439. Note that this functionality is documented:
|
Yes, the isses are substantially similar. The documentation is incorrect though: it should say that flag may call .String() on a zero value of the flag type, or if the flag type is a pointer, on a pointer to a new variable. Not a precondition that exactly rolls off the tongue; frankly I'm surprised this wasn't considered a breaking semantic change to a published interface. |
What is "this" that was a breaking change? |
Happy to see that rolled back. I didn't like it at all at first, and only a little bit later on. |
The simplest thing would be to print |
@jimmyfrasche, or we could record whether the default value is the zero value of the type when we first set (We could even do that without changing the definition of |
@bcmills that would work reliably with all flags satisfying the optional For It would be much superior to the status quo in that it would never explode. It would probably do the right thing most of the time. A nontrivial |
Why don't we just roll back the change? |
The change in question (https://golang.org/cl/23581) fixes #15904. Of course we can decide to not fix that, though I'll note that it was reported more than once. |
Checking against a whitelist of zero values is simpler to maintain, but it has false positives and negatives for user-defined flags. In addition to not recognizing "0s" there was #23543 where a string flag with "0" was being falsely recognized as a zero value. Only checking against the empty string is even simpler. It's also uniform in that it works the same for any flag, regardless of how or where it is defined. It checks against the shell's concept of a zero value rather than Go's, so it may be of greater value to an end user unaware of Go's concept of zero value. Debatable. Using reflect to see if it's the zero value is uniform and extensible but less simple to do right, empirically. Perhaps @bcmills' version would work if it were simplified to only run if there's a Another option would be yet another optional method on |
@ianlancetaylor But the fix is problematic. We can roll back this fix and handle #15904 another way, rather than continuing to hack and redesign and complicate a fundamentally simple package. |
If someone has a different fix that is certainly fine with me. |
I did a sketch of my simplification of @bcmills' check in I'm guessing it would be roughly the same LOC as the current fix but it would be more reliable and less surprising. |
Issue #24439 already cover the documentation being incorrect, see here:
This is the same as @alandonovan said above, just from the The mentioned breaking change broke one of my type myFlag struct {
name string // either "slow", "normal" or "fast"
ptr *string // shared pointer to the selected mode
}
func (f myFlag) String() string {
// return strconv.FormatBool(*f.ptr == f.name) // panic
return strconv.FormatBool(f.ptr != nil && *f.ptr == f.name) // fixed
} I made a zero-valued I think the fix proposed here is simpler and easier to document. Users would have to make adjustments to their code again to avoid printing redundant defaults, but that's a minor issue compared to the panic (in my case I would have to add the |
Change https://go.dev/cl/396079 mentions this issue: |
This bit me recently. In a closed-source project I have implemented a dynamic flag type that can be set either on the command-line (through package However, my concrete type may represent many different types of flags (strings, ints, other more complex types), so the zero-value reflection trick employed by isZeroValue always generates a useless struct that doesn't know about the type of data it's supposed to hold. I was very surprised to see my programs panic when printing help output, and we didn't even notice this was happening until about a week later. I think the approach, while clever, simply doesn't work for some concrete implementations of My proposed change is to test for a One question is whether the |
Hi @adg. Looking for a |
Change https://go.dev/cl/396354 mentions this issue: |
Thanks for the quick response @iant. That being the case, I have sent a change that takes the more conservative first step of simply using the interface for the built-in flags only. This addresses the main issues raised in this thread, with the minor downside that some redundant |
I wonder if |
@iant Yes. That's what the fmt package does, for example. |
Sent https://go.dev/cl/396517. |
Change https://go.dev/cl/396517 mentions this issue: |
Change https://go.dev/cl/396574 mentions this issue: |
(*flag.FlagSet).PrintDefaults assumes that it is safe to call .String() on the zero value of a flag type, or if that flag type is a pointer, a new instance of the variable. Consequently, a program that uses indirect data structures in its flags will panic when a user sets the -h flag.
I don't think the flag package should be making assumptions about the concrete representation of the flag. That's the point of the flag.Value interface.
The text was updated successfully, but these errors were encountered: