The spec says that "The main package must have package name main and declare a function main that takes no arguments and returns no value [emphasis added]."
I propose to additionally allow returning error. Combined with the check and default-handler mechanisms suggested in the Error Handling Go 2 Draft Design, this could reduce some boilerplate. It would be equivalent to:
// main is the entry point.
func main() {
if err := main1(); err != nil {
os.Stderr.WriteString(err.Error() + "\n")
os.Exit(1)
}
}
// main1 is what the programmer writes as "func main() error".
func main1() error {
// etc
}
This is admittedly sugar, but these days, I write that trivial main-calls-main1 dance in most of my package mains. For example:
To be clear, func main() { etc } that returned no value would still be valid, and have unchanged semantics.
This is similar to issue #21111 but this one is for main functions and that one is for testing functions (including test examples, which are somewhat similar to main programs).
Indeed, the Error Handling overview says:
It would be shorter and clearer to write instead:
func main() {
handle err {
log.Fatal(err)
}
hex := check ioutil.ReadAll(os.Stdin)
data := check parseHexdump(string(hex))
os.Stdout.Write(data)
}
It would be even shorter, and I think even clearer, if func main() was func main() error and we dropped the explicit handle block:
func main() error {
hex := check ioutil.ReadAll(os.Stdin)
data := check parseHexdump(string(hex))
os.Stdout.Write(data)
}
I too often end up writing three line main functions that call another function which returns an error, but I think if handle blocks were accepted then I could just put that code into the handle block and not use the wrapper, so this wouldn't be as helpful.
Anyway, if main returns a value, why not have it be an int for the error code? And why not have main take a slice of os.Args? Those are both common in other languages, so I assume it was omitted for a reason.
I'm not sure I understand correctly, but would it be addressed if we special-case main.main's default handler? That is, for main.main (and only main.main), the default handler is
handle err {
log.Fatal(err) // or any other form of print+exit
}
I think even if we allow main.main return an error, there still needs a default/predefined/magical wrapper of main.main that does the log.Fatal or os.Exit thing? If there will be one of such thing anyway, would it just be main.main's default handler?
It could just be a default handler for main.main, I suppose, although my instincts are that a default handler seems more special-case-y than letting it return an error. OTOH it wouldn't need to change any tools that assumed that main.main returned nothing.
Or I could just keep writing the explicit code that I already do. It's slightly simpler with the draft-suggestion handle mechanism, as I wouldn't need the main / main1 split.
Similar suggestions are to let init functions also optionally return error and also somehow let global variable initializations use check. With hindsight, that would have eliminated the need for Must-y functions like template.Must and regexp.MustCompile.
why not have it be an int for the error code?
Well, Go's error type is richer than ints. It can be an arbitrary error message, e.g. including the filename of things that failed.
I'm also not sure if "programs return int" is universally applicable or just a Unix-ism.
why not have main take a slice of os.Args?
Yeah, I'm not sure why it wasn't func main(args []string). Maybe they didn't want to overfit to Unix. I'm just guessing, though.
I suggest a read through the feedback wiki
https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback
Most of it is counter-proposals; I suspect the next draft will be rather different :-)
In non-returning functions (main & goroutines) I suspect a "default handler" -- or explicit one that doesn't os.Exit() -- would panic(err) so that deferred ops run. If you want a log.Fatal(err), you have to code it.
Oh, I'm sure that the next Error Handling draft will be rather different. I just wanted to write this idea down before I forgot.
Related: https://github.com/golang/go/issues/24869, which proposed that main.main be allowed to return an integer exit code (but was quickly rejected).
This seems like a clear "no" to me. If anything, the new handle-check proposal is an argument _against_ doing this, since if we adopt that proposal you'd be able to write:
func main() {
handle err {
log.Fatal(err)
}
... check os.Open ...
etc
}
I see very little compelling benefit here and I strongly disagree with establishing a standard handling of what happens when main returns an error. Your example of by default printing the error and exit 1 is one possibility but there are plenty of others. Better to let main be in charge of that.
More generally, we've already made the decision about what main is. I don't see a strong reason here to reopen it.
Most helpful comment
Similar suggestions are to let
initfunctions also optionally returnerrorand also somehow let global variable initializations usecheck. With hindsight, that would have eliminated the need for Must-y functions liketemplate.Mustandregexp.MustCompile.