-
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
proposal: flag: add Fatalf #21540
Comments
Another point in favor of not hard-coding |
It might be useful to take a couple of existing Go programs and rewrite them using this hypothetical API addition to see what it looks like, and how much it saves. Some potential concerns:
Possible simplifications:
|
I'm really excited about this proposal; I am frequently frustrated by the amount of boilerplate involved when checking flags for correctness. On reflection, I'm in favor of only adding the top level As for |
Sure; I'll send a CL tomorrow (after the one with this API change) with a bunch of changes using the new API.
Agreed. When I said
The point of
I share Dmitri's concern. However, there is little precedent for having all three besides convenience - and this is such a niche API, that I'd say
Sounds like a good idea, and I agree with Josh that those defining their own flagsets are probably fine with more boilerplate and should get all the details right.
While I would normally agree, I don't think it's a good idea in this case. Not because of the reflection needed, but rather because noone would depend on a third-party library to avoid two extra lines of code. I think a CL showing its impact on the standard library itself should be enough on its own. If anyone wants to write a package and recommend its use, by all means go ahead; I just don't think it's worth it and won't do it myself. |
Great, thanks. I'm eager to try this sooner, so this will be a thought experiment/case study on the Its current relevant code is this: func main() {
flag.Usage = usage
flag.Parse()
if *origin != 0 && *origin != 1 {
fmt.Fprintf(os.Stderr, "ivy: illegal origin value %d\n", *origin)
os.Exit(2)
}
if *gformat {
*format = "%.12g"
}
conf.SetFormat(*format)
conf.SetMaxBits(*maxbits)
conf.SetMaxDigits(*maxdigits)
conf.SetOrigin(*origin)
conf.SetPrompt(*prompt)
if len(*debugFlag) > 0 {
for _, debug := range strings.Split(*debugFlag, ",") {
if !conf.SetDebug(debug, true) {
fmt.Fprintf(os.Stderr, "ivy: unknown debug flag %q\n", debug)
os.Exit(2)
}
}
}
...
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: ivy [options] [file ...]\n")
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
os.Exit(2)
} Given your proposal so far, I believe that code can be changed as follows: func main() {
flag.Usage = usage
flag.Parse()
if *origin != 0 && *origin != 1 {
- fmt.Fprintf(os.Stderr, "ivy: illegal origin value %d\n", *origin)
- os.Exit(2)
+ flag.Fatalf("ivy: illegal origin value %d\n", *origin)
}
if *gformat {
*format = "%.12g"
}
conf.SetFormat(*format)
conf.SetMaxBits(*maxbits)
conf.SetMaxDigits(*maxdigits)
conf.SetOrigin(*origin)
conf.SetPrompt(*prompt)
if len(*debugFlag) > 0 {
for _, debug := range strings.Split(*debugFlag, ",") {
if !conf.SetDebug(debug, true) {
- fmt.Fprintf(os.Stderr, "ivy: unknown debug flag %q\n", debug)
- os.Exit(2)
+ flag.Fatalf("ivy: unknown debug flag %q\n", debug)
}
}
}
...
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: ivy [options] [file ...]\n")
fmt.Fprintf(os.Stderr, "Flags:\n")
flag.PrintDefaults()
- os.Exit(2)
} That looks like a pretty nice simplification to me. There were rejections of removing |
Glad to hear that. To me, the most important part is reducing the amount of things that developers can get wrong for no good reason.
I think you're mixing two slightly different issues here. One is calling The other is having an However, I think this proposal holds enough value on its own, and that the " |
Change https://golang.org/cl/57311 mentions this issue: |
We would prefer you wait for the proposal to be accepted before sending a code review implementing it. The discussion should be here, rather than in the code review, plus it might not be accepted. |
Of course - I didn't spend too much time on the CL since I know it might never get merged. I'll change it to "DO NOT SUBMIT" to make it clearer. As for the discussion, note that all that's being talked about in the CL is the godoc and the test implementation. I would redirect any proposal discussion here, but there hasn't been any over there. |
I'm slightly in favor of this proposal. I rarely write tools that need to handle flags, but when I do, I always have to look up the pattern for correct error/usage/reporting. |
Flag already has enough semantics around error-handling. I don't believe we should add more. I assert the following:
Any attempt to introduce new API here doesn't invalidate any of the above, so new API is not necessary. If new API were added, it seems clear from the discussion and past pull requests that the next thing that would happen is there would be many changes going around trying to update code to use flag.Fatalf instead of flag.Usage (in programs where flag.Usage has been reassigned to a function that exits). That's completely unnecessary churn, and cutting off that churn seems to me an equally good reason not to introduce new API, even beyond the fact that it's unnecessary. If not for testing, I think flag.Usage would have exited from the start, but changing that now is an example of something that's high cost, low reward.
|
Also, note that in the proposed rewriting of ivy above, the diff changes two error exits that did not print the flag default dump into error exits that do. That seems undesirable to me in general (and, I suspect, to @robpike, or he'd have called flag.PrintDefaults explicitly). In many of these cases, dumping all the flags doesn't actually add to the error message - they're just noise - and we don't want to make the low-SNR outputs the ones that are easy to generate. |
I'm convinced. We should not do this. |
It is unclear. Adding words to the doc comment making it explicit would make it very clear. In general, I don't believe it's possible to determine that something is correct from seeing many examples of it, especially when what's being done is counter-intuitive. It could be that many people are making a mistake without realizing it (and many of them just copied the code, without understanding it, from one another). That's why the only reliable source is the documentation. I agree with the arguments presented and happy with the outcome, as long as the |
Thanks for the detailed counter-argument, @rsc. I'm a bit confused by how it centers around However, that doesn't answer the other three points made in my original post. The boilerplate was the main argument of this proposal, and even though two lines are fewer, I also think it's still a point to consider. I too am OK with this proposal being declined if there is no consensus. That should be the outcome for a proposal that intends to add new API. I would prefer to have this API out of convenience and to not have to |
@mvdan, yes, sorry the discussion kind of diverged into flag.Usage and os.Exit. I do see that your original post was not directly about that. My second reply above was the important one for the original proposal: if you make it easier to "print a message, print the usage, and exit", people will do that too often. The vast majority of the time, printing the full usage is mostly not helpful. Using your example in the original post above, if I pass an invalid value to -bar, it's good to point out my error using -bar. It doesn't help to then print information about 10 unrelated flags, but if you introduce the flag.Fatalf above, that's what will always happen. If anything I think people call flag.Usage too often (the vet example being one instance of this). In my view the real problem here is that log.Fatalf is unusable in this context without proper initialization. If I had it to do over again (and I don't believe I do), I would make
the default settings for package log, and then the right thing to do would be to call log.Fatalf, not a new flag.Fatalf that also prints mostly unrelated information. |
I agree that more often than not, printing the usage information is not very helpful. However, would that not apply to the way the flag package errors on invalid flag values too? In the It's an interesting point you raise about the And it would also exit with exit code 1 instead of 2, which I think is fairly important. There's always the option of introducing |
Declining this proposal but I filed #21671 for documenting that flag.Usage implementations can exit. |
The Go team clarified that calling os.Exit in usage functions is perfectly fine. See the following links for further information: - rsc/getopt@9e2cef2#commitcomment-23601620 - golang/go#21540 (comment) - golang/go#21671 Closes gh-159
Problem
Setting up command-line flags with
flag
is easy. For most cases, the built-in restrictions are enough - for example, if one wants to accept only unsigned integers, one can useflag.Uint
.Adding one's restrictions to a flag is also fairly straightforward by implementing the
flag.Value
interface. For example, we could use this to enforce that a flag only accept integers between 1 and 10:However, there's another kind of restriction that is common - the kind that concerns multiple flags or external factors. Let's take one example right from the standard library, from
cmd/vet/all/main.go
:At the time of writing, the best way to implement such restrictions is by printing the error, calling
Usage()
and callingos.Exit
. This has several shortcomings:Usage
.os.Exit(2)
call, instead of letting theflag
package handle exiting itself.fmt.Fprint{ln,f}(os.Stderr, ...
yet this one callslog.Print
.flag.SetOutput
.The last point, while important, could be solved with #17628. However, that would add a bit more complexity to the code that everyone must write correctly, and I'm sure plenty of implementations would keep on hard-coding others like
os.Stderr
.Proposed solution
I propose adding two new functions to the
flag
package:Then our real example from above would become simply:
And it would have zero margin for error, including its use of
log
and the missingflag.Output()
piece.I'll submit the patch with this very change now. Happy to work on transitioning all these cases in the standard library to
Fatalf
if it gets accepted and merged.CC @josharian who had a similar idea and gave his input
CC @dominikh who might be interested given his other flag package API issue
CC @robpike as per golang.org/s/owners
The text was updated successfully, but these errors were encountered: