-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: exp/xerrors: add try and handle #67913
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
u/Enough_Compote697 has assigned me as his communication agent, and he has just sent me a supplementary note. The
Update: u/Enough_Compote697 argues that
Update 2: u/Enough_Compote697 refactored the error handling code to a handling function generator like P.S. For other custom |
For anybody interested in defining an
Please file another issue. It is out of the scope of this proposal. Thanks for your cooperation. (In reality, in order to make type assertion like |
"Don't forget the cost of education and documentation as exemplified by the introduction of u/Enough_Compote697 also kindly reminds prospective commenter to recap wiki/NoPlusOne. |
I suggest changing the signature of Example: play/p/WoYEctU1d0P
Update: the The signature and implementation of the
In this version of |
Update: idea retracted (see the comment below)
"In my test, the Note: In this revision, |
Comment from u/Enough_Compote697: I believe we all should be aware of feature creep and keep in mind that this proposal is for the most common scenario of error handling, i.e. propagating the error to the handling function. Therefore, I suggest keeping the scope of this proposal as is. If anyone wants an extra conditional handling function, please write your own custom error handler or file a separate proposal for it. Thanks for your cooperation. P.S. In my personal experience, when I need to to write error inspection code like Again, without data supporting such addition, I would like to keep the scope of this proposal as is. That said, I am open to such proposal, so just feel free to comment. Your feedback can help moving the discussion forward. :) |
I don't think we need a mirror of a reddit discussion in the issue tracker. |
Proposal Details
Disclaimer: The following was originally posted by u/Enough_Compote697 on r/golang. For greater reach, and especially for the attention from the Go team, re-post here after the deletion of the original post. Please keep in mind that I am NOT the OP of that Reddit thread, so please don’t tag me in this GitHub issue (unless reposting violates the rules here).
To encourage robust programming by reducing error handling boilerplates, I propose promoting the following functions from a third party library to
exp/xerrors
and eventually making them built-in:Technically, a built-in variadic
try()
would eliminate the need ofcheck
,try2
,try3
, etc.Before commenting/voting on this proposal, please make sure you are familiar with the Reddiquette, and please abide by it the best you can.
Examples
Ex. 1:
Ex. 2: (see blog/errors-are-values)
Note that with the
try
/handle
pair, this version offlush
is much simpler and cleaner than theerrWriter
example in the blog, and much less code is needed.Ex. 3: (see issue/32437)
Ex. 4: (see play/p/bOUz7qx3urD and the corresponding r/golang post)
Ex. 5: (see design/go2draft-error-handling-overview)
Ex. 6: (see design/go2draft-error-handling
To prevent mishandling deeply nested error that is out of interest, one may limit the error type to a specific kind, and handle the error only after a type assertion, e.g.
Ex. 7: (an alternate version of ex. 6, changes marked with ~)
Since both
return
andpanic
are for propagating error back to themain()
function, they are nearly identical semantically. That said, if library authors dislike this kind of error propagation, they may just keep using the current idiomaticif err != nil
to handle errors, or adopt the handler propagation style illustrated above.In addition, even though this version of
SortContents
panics instead of returning an error, the return argument (error
) in the function signature can be kept for documentation purpose by treating the signature as a kind of function annotation, but the cost is to add a corresponding trailingreturn nil
statement. Although the compilation will fail if this final line is omitted, it seems like a gotcha with stinky code smell.Ex. 8: (another alternative as in
regexp
andtext/template
)Note the idiomatic
Must
prefix implies the function panics in case of error. Although it is up to callers to decide whether to ignore or handle the error in case the program panics, personally I am more inclined to proper error handling rather than turning a blind eye on it, as this proposal is all about encouraging robust programming.Motivation
When there are too many trivial error handling surrounded by boilerplates, their non-trivial counterparts will just nearly disappear, so this proposal tries to promote the visibility of truly important red flags by reducing boilerplates.
Also, by reducing the burdens on programmers, they are less deterred from handle error robustly. (See the law of effect and operant conditioning for more on the topic.)
Scope
This proposal is only about adding 2 functions (
try
andhandle
) to the official repository. Although the introduction of this pair may sparkle debate on error handling style (handler propagation vs error propagation), this proposal is not intended to set a new convention. That is left for the community to discuss and experiment on how to use these 2 functions. If you prefer the idiomaticreturn err
, it is fine, and you can just keep using it. This proposal is not aimed at replacing that idiomatic error handling style but just at reducing programmers’ burden by promoting totry
/handle
pair from a third party library to the official repository, so please keep in mind that the discussion here should focus on the cost and benefits of such promotion. Thanks for your cooperation.(Expected) Benefits
(Expected) Cost
Basically, when it comes to programming proposal, it is all about trade-off in the following trilemma:
The latter two can be addressed with technical improvement. For example,
go1.14
reduce the cost ofdefer
with design/34481-opencoded-defers. Although the benchmark then might now become slightly outdated, the tax oftry
/handle
still seems far from being prohibitive.Regarding programmers’ time, it can be further broken into learning time and coding time. Reducing boilerplates means saving coding time in a recurring manner, so the one-time cost of learning looks like a worthy investment, not to mention the potential robustness enhancement.
Comparisons
design/go2draft-error-handling
: change statements to functions to enhance composablility (see examples above)design/32437-try-builtin
: change implicitreturn
to implicitpanic
to reduce surprises, as implicitpanic
already exists while implicitreturn
is something new and surprising, violating the principle of least astonishment.FAQ
Q: Why revisit the
try
built-in proposal?A: To reduce boilerplates of error handling. It is a pain point of many Go developers according to past surveys.
Q: Why not the idiomatic
if err != nil { return }
flow but the foreignthrow
-catch
model?A: When most library routines encounter an error, they immediately abort and return a non-
nil
error coupled with 1/2 negligible zero value(s), so replacingreturn
withpanic
means nearly semantically the same for the caller.Moreover,
return err
can coexist with thepanic
-recover
model, so library authors can choose the error handling flow that fit their needs the most.Q: But that violates the Go philosophy. There should be one (and preferably only one) obvious way to do it.
A: That’s true, but there is nothing new. This proposal is not about adding a 3rd way of error handling to the language, but just about utilising the existing language constructs to reduce boilerplates.
Q: How about
go try(f(…))
?A: Not clear. Before understanding how goroutines would impact
defer
/panic
/recover
, it seems prudent to forbidgo try(f(…))
first byvet
check forexp/xerrors
during the transitional stage.Library authors can keep using the good old idiomatic
if err != nil
check with goroutines as is.Discussion
Examples above simply convert
return err
topanic
. More complex control flow likegoto
/break
/continue
may need extra care when switching the error handling flow to thepanic
-recover
model. In case simple refactoring is not enough, perhaps just remaining as-is (if err != nil
) suit such scenario the most. Without real world data, it is hard to tell whether the task of refactoring is easy or daunting. If there are real world examples demonstrating non-trivial refactoring, let’s see if we can write tool to automate the work as in therangefunc
experiment. For example, given a function:package foo
//go:generate witherrhandle
func f(v T) error {
return g(v)
}
Running
go generate play.ground/foo
yields:For the sake of compatibility, current
stdlib
routines would keep panicking or returning errors as is, but the internal may use the new mechanism as long as such changes are invisible to callers. When the timing becomes mature, signatures of newly added functions may form a new idiomatic flow of error handling as shown above.From a dependency management perspective, promoting
handle
from a third party library to the official repository, meaning establishing a mechanism for panic interception, may do more good than harm, but the pros and cons are not so clear, so it is left for the community to discuss. For example, if packagemain
imports packagefoo
, and the input sanitizer offoo.Bar
panics like theMustXXX
family in packageregexp
andtext/template
, themain
function, especially that of critical server, would be better-off with panic recovery and graceful error handling.Alternatives
The
try
function can be written in a variadic fashion with some cost illustrated below:func try(vs ...any) []any {
if e, ok := vs[len(vs)-1].(error); !ok {
panic("try: last argument is not error")
} else if e != nil {
panic(e)
}
return vs[:len(vs)-1]
}
Although this version of
try
is variadic, it is much less user-friendly. Since it returns[]any
instead of concrete types, type assertion is needed for devirtualisation. For example, to open a read-only file, one has to writeIt will be even more clumsy when devirtualising more than one
interface{}
.try
is technically most suited to be a built-in function,handle
is less constrained and it can be a function in theerrors
package instead. Although where will it land eventually is out of scope of this proposal, please feel free to discuss here about the pros and cons of adding one more reserved word. In particular, ishandle
frequently used as a custom variable/function name? If so, it means the built-inhandle
will be often shadowed, rendering it less useful to be a built-in. On the other hand, a nice side-effect of moving thehandle
function to theerrors
package is the possibility to define a visually pleasanterrors.Handler
interface.Postscript
Although this proposal is just about including a pair of trivial functions in the official repository, it is surprisingly hilariously disproportionally lengthy, reminding me the Parkinson’s law of triviality. The reason is that much length is used for highlighting the differences between the new and the idiomatic error handling flow so as to address the Fredkin’s paradox, psychological inertia and path dependence. Nonetheless, if clearer explanation can help us reach consensus more easily, it is worth writing more words.
Acknowledgement
Thanks all for you thoughtful feedback, including but not limited to u/FullTimeSadBoi, u/justinisrael and u/drvd. Please accept my apology if I have forgotten to mention your name.
The text was updated successfully, but these errors were encountered: