Reputation: 107
I am developing a CLI tool and in cases where the things go wrong, I want to log the custom error and exit with panic. The issue with panic is that an exit by panic
is followed by the stack trace which I don't want to show to the user. Is there a way to panic and have a ninja-like stealthy/quiet exit?
(Choosing panic
over os.Exit() since that would handle any defer
and also seems a lot cleaner.)
Upvotes: 1
Views: 2225
Reputation: 55463
Well, a direct answer is yes, there is a way:
panic
with an error of a custom, "sentinel", type, or with a custom, "sentinel", value, thendefer
-red call in main
which recover()
-s the panic and checks whether the returned value is of a sentinel type (or equals to a sentinel value—the exact approach is up to you).panic
s again.But honestly I think your mindset is too affected by programming languages with exceptions: I have yet to see a CLI app which would have any difficulty in handling errors "the usual way" or would actually benefit from bailing out using panic
.
A counter-argument to the approach you're craving for is this: bubbling up an error allows adding more context to it on each level of the call stack being unwound, where it makes sense,—producing as useful as possible error to display. Basically, it works like this:
func main() {
...
err := DoStuff()
if err != nil {
log.Fatal("failed to do stuff: ", err)
}
...
}
func DoStuff() error {
foo, err := InitializeWhatever()
if err != nil {
return fmt.Errorf("failed to inialize whatever: %w", err)
}
...
return nil
}
func InitializeWhatever() (*Whatever, error) {
handle, err := OpenWhateverElse()
if err != nil {
return nil, fmt.Errorf("failed to open whatever else: %w", err)
}
...
return whatever, nil
}
…which would produce something like
failed to do stuff: failed to inialize whatever: failed to open whatever else: task failed successfully
…which makes it crystal clear which sequence of events led to the undesired outcome.
Sure, as usually, YMMV and no one except you knows your situation best, but still it's something to ponder.
And here's an assorted list of thoughts on what I've written above.
net/http.ErrAbortHandler
.encoding/json
package used to employ this approach for breaking out of multiple nested loops (has been reworked since then, though).net.Error
has Timeout
and Temporary
methods which allows to not have concrete exported types for temporary errors, and errors due to timeouts, and their combinations,—but instead have them all support a common set of methods which allow the callers to make sense of the error's nature.Well, and I'd recommend reading this for, I must admit, had you already done this, you'd probably not ask your question in the first place ;-)
Upvotes: 4