-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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: Panics returned as errors if the function has an error result type #32871
Comments
A Turning that Moreover, unlike other run-time errors, a captured nil-panic won't include additional details that may be helpful in diagnosing the bug. And there are some cases where a function with an I would certainly love to see improvements that address the root causes of nil-panics in Go, but not at the expense of detecting actual bugs. Instead, I would rather see improvements to fuzzing (#19109), more nil-safety in the type system (#28133 and others), and/or static analysis tools. |
Apart from whether this change is a good idea or not, I don't think we can make this kind of change in the Go language today. It would cause too many existing, correct, programs to silently break in cases where they occasionally, but rarely, panic. |
Fair enough. To be honest, I agree with you about addressing the root cause.
After the discussion, my feeling was that there is a lot of resistance to doing changes (like the one discussed in the first comments about the zero value), so I tried to come with a "compromise solution" to "nil-pointer dereferences" errors to reduce crashes. In any case, I would say that nil-safety should be addressed sooner rather than later. I understand that testing and code-reviews can catch nil errors, but they are not foolproof (unlike a compiler-based solution). If they were, there wouldn't be nil errors in production code, and there are. I'll keep giving this a thought to see if we can get to a good solution 🙂
Thanks for your comment. Could you please elaborate a bit? (I'm genuinely asking) |
@alvaroloes Interesting, as I have the exact opposite intuition. I don't think I can show a useful small example, as I am explicitly talking about cases that happen rarely. In case it wasn't clear, I'm talking about large existing programs that were written before this change to the language. For some of those programs, a |
Thank you, Ian, for the explanation. I still don't see it clear why an application would continue normally after an error has occurred (and just log it). In any case, I prefer to close this issue and dedicate the time and effort to another one that fixes the root cause of nil panic. Thank you all for your time and energy for reading and commenting on this issue! |
On 7/3/19, Álvaro López Espinosa ***@***.***> wrote:
Thank you, Ian, for the explanation. I still don't see it clear why an
application would continue normally after an error has occurred (and just
log it).
Very briefly, you are correct in a general sense, but you cannot
compel every other developer to accept your preference in this case.
Code has been written, good and bad, no doubt, and will continue to be
written, that conflicts with that.
|
I agree with you @lootch, and I understand that better or worse code can be written. For example, one could use panic-defer-recover as logic control flow and create a correct program that does its job. The real deal of all changes to the language that are not fully backward compatible is that the ratio "value/(downsides * affected applications)" must be pretty high. It is a bit difficult to demonstrate that for this proposal, so the justification only comes from subjective opinions (which is not enough). As I said, I'll keep thinking of another better proposal. Thanks for your comment. [thinking out-loud]
[/thinking out-loud] |
On 7/3/19, Álvaro López Espinosa ***@***.***> wrote:
[ ... ]
>I wish Go would have had that into account since the beginning, as it is
> **really** hard now to add those changes in an orthogonal and
> backward-compatible way.
That mantra is perishable goods. I think the "try" efforts show that:
keeping things the way they are is a "single truth", otherwise called
"the devil you know". The moment you need to move away from that and
there are multiple possibilities that cannot be accurately measured
against each other, only good politicians know how to keep the
community from splitting or the split from becoming a disaster.
Technologists are almost by profession the worst negotiators and
should be as conservative as the unreliability of their (social)
measurements compel them to be. Pioneers and adventurers can take
risks, engineers can't.
|
@alvaroloes We can aim for a better paradigm, if we believe it is better, but my point is that we can't silently change the behavior of existing code. We can introduce new mechanisms, we can remove old mechanisms, but we can't silently change old mechanisms to new ones. There is too much existing code for that. |
Thank you both for the comments.
Gotcha. Let's keep trying. I'll keep spending a bit of my spare time to find some "mechanism" (with a positive value/downside ratio) to avoid those implicit panics. |
@ianlancetaylor @bcmills @lootch Here is a new proposal to try to have nil-safety checked at compile time: #33078 |
TL/DR
Make
panic
stop "bubbling up" in the call stack when it finds a function with anerror
as last result type and return the panic content as that error.This will reduce a lot of crashes due to "nil-pointer dereferences" (among others), making the application more stable and suitable for long running tasks (like servers).
Disclaimer
The entire proposal is based on my experience and point of view. The main goal is to validate it against the Go community to see if it makes sense and to check if other people share the concerns this proposal try to address.
Motivation of this proposal
To avoid (or reduce to the minimum) unexpected application crashes.
In Go, it is pretty easy to crash the entire application by an implicit
panic
(like a nil pointer dereference). It is even easier than in other languages because of the implicit dereferencing of pointers-to-structs variables.This adds a feeling of insecurity when writing code, forcing the developer to be extra cautious and add checks in the code that increase verbosity and reduce readability.
Hopefully, this proposal will resolve that.
Proposal
When a
panic
occurs, it starts "bubbling up" until it finds a function that contains adefer
with arecover
. If it is not found, the application crashes.This crash is (almost?) always not the desired result (from my experience), as you can't recover from it. The only way is to restart the application.
To avoid crashing, if you know your function can panic, you can add the "defer-recover" construct.
However, it is not always evident whether the function you are calling can panic (even if you look at the source code), so it is really easy to forget adding the "defer-recover" construct.
This is especially true with "nil pointer dereference" errors, as they are hidden in apparantly good code and can be dormant for a long time until they appear. This can happen with any Go "pointer type": pointers, functions, maps, channel, and interfaces (not with slices).
For example:
This code could panic and make the application crash. You can follow two approaches to avoid the crash:
This is just a very simple example. Things get worse when you are working with interfaces holding pointers.
This proposal's goal is to be able to get the safe version of the code but without any change in the logic (as long as the function already has an
error
return type)How? With the following new rules
Rule 1: Whenever there is a
panic
(implicit or explicit), follow the current "bubbling-up" rules plus the addition of rule number 2.Rule 2: When a function with an
error
as last return type is found, stop panicking and return the error with the contents that would have returnedrecover()
. The rest of the return values are filled with the zero value as expected.With this addition, when we see a function like this (for example):
We are completely sure that the only way that function can finish is by returning
nil
or anerror
. No unexpectedpanics
are leaked.Implementation details
I don't have enough knowledge about the Go source code to provide implementation details.
My guesses are that implementing this proposal should not be too hard. It could be summarized as the following:
I guess there could be complications around the
err
variable, as it should be the namederror
return value.Surely there are better ways to implement this, but the goal now is to know the opinion of the community to first verify if the proposal makes sense.
Backward compatibility
I would say that, for 99% of the cases, this change will be backward compatible.
That 1% of the cases are those programs that are using the "panic-defer-recover" construct for doing actual logic control flow.
Of course, those numbers are totally made up. Maybe there are more cases, but I prefer first to focus on validating the idea with the community first,
FAQ
1.- Would this only work for "nil-pointer dereference"
panics
?No, it would work for all kind of panics, like
index out of bounds
panics.2.- What to do when the user explicitly panics?
This will work for both implicit and explicit panics. It is true that when your function can return an error, doing:
would behave in the same way as:
But the question here is, if your function can already return an error, Why would you panic instead of returning the error?
I'm really asking. I don't know if there is a case where it is preferable to panic than to return an error
3.- What happens if there is a defer that calls
recover
and the function has an error return type too?It would behave in the same way as today: the defer will be executed, the panic will be recovered, and the function will return as if no panic happened.
The "conversion" from
panic
to an error only happens if the panics "leaks" the function.4.- What if no function with an error result type is found?
The application crashes 😞 . But this is what should happen, as no "error recovery code" was found in the whole call stack, so the only thing the application can do is to kill itself.
The text was updated successfully, but these errors were encountered: