The cost of Go's panic and recover
28 comments
·March 1, 2025rob74
aqueueaqueue
You know why I hate exceptions most?
When debugging be it C# or JS, neither the "break on all exceptions" or "break on caught exceptions" are useful on any app. One just hits random library shit, or whatever bloat is in the codebase all the time and the other won't break at all.
But because exceptions are the control flow that is the only way to debug them (or do a human binary search)
Not sure what go debugging is like but I imagine you can quickly work your way to the first err!=nil while debugging.
mrighele
I don't know about C#, but in my Java IDE when I set a breakpoint on an exception I can set a filter not only on the class being throw, but also on the class that catch it, the one that throws it and the caller method, and to trigger only after another breakpoint is hit or it is the nth times it has been passed. With this you can make it trigger only when needed in a farly easy way
CharlieDigital
It's the same for the mainstream debuggers for .NET.
rs186
Well, "break on exceptions" can be very powerful when used correctly, i.e. when the scope is narrowed down. It should never be a flag that is turned on all the time -- that's guaranteed misery there.
> quickly work your way to the first err != nil while debugging
I doubt you'll spend any less time debugging in Go. If you disagree, I'd love to see a "side-by-side" comparison for code that's functionally the same but written in both Go and JS, and see some explanations why it's easier in Go
7bit
You can limit those to code you write. But it sounds like you break also on code that you didn't write eg, libraries or modules. Of course you're miserable.
9rx
> which actually only means "things aren't working as expected"
Exceptional circumstances, or exceptions for short, mean "things aren't working as expected due to programmer error". In other words, a situation that theoretically could have been avoided by a sufficiently advanced compiler but that wasn't caught until runtime.
"things aren't working as expected" is vague enough to include errors, which are decidedly not exceptions. One might say a hard drive crash or the network failing isn't working as expected, but those situations are not exceptional.
> to make clear that it shouldn't be used where you might use exceptions in other languages.
Other languages are starting to learn that you shouldn't use exception handlers where you wouldn't use panic/recover, so I'm not sure there is a practical difference here.
daveliepmann
>Exceptional circumstances, or exceptions for short, mean "things aren't working as expected due to programmer error".
Interesting. In Javaland this describes assertions, and the term exception is for operating errors, i.e. problems not necessarily attributable to programmer error, including your example of a network failure.
bazoom42
Terminology is a problem here. A crashed harddisk is clearly an exceptional circumstance. More specific terms is needed to distinguish errors in the code (eg divide by zero) from unpreventable errors like network failure.
jjmarr
I've solved n-queens once before using exceptions to handle control flow. I coded a recursive solution for an assignment, but I wrote it wrong and it ended up printing all of the possible solutions instead of just one.
Because I didn't have much time before the final submission, I just put the initial call in a try catch block and threw an exception to indicate successful completion.
actionfromafar
Enterprise ready. :)
shric
I programmed in Go for 5 years (stopped 2 years ago) and didn't even know the language had recover() until 5 minutes ago.
I used panic() all day, but never recover. I use panic for unrecoverable errors. I thought that's why it's called "panic".
williamdclt
`recover` is still useful for unrecoverable errors, eg to capture telemetry then propagate the panic again
troupo
In most software there's no such thing as unrecoverable panic. OOM is probably the only such error, and even then it doesn't come from within your app.
For all "unrecoverable panics" you usually want to see the reason, log it, kill the offending process, clean up resources, and then usually restart the offending process.
And that's the reason both Go and Rust ended up reverting their stance on "unrecoverable panics kill your program" and introduced ways to recover from them.
Ferret7446
Go never had a stance on "unrecoverable panics kill your program". Go always supported recover, but encourages (correctly IMO) error values because they are more performant and easier to understand. The Go standard library even uses panic/recover (aka throw/catch) style programming in specific instances.
troupo
> they are more performant and easier to understand.
They are more performant because Go decided to make them so. E.g. in Erlang crashing a process is an expected lightweight operation.
As for "easier to understand"... They are not when:
- your code is littered with `x, err = ...; if err != nil`
- it's not easier to understand when the code errors have to be dealt with on a higher/different level. The calling code isn't always the one that needs to deal with all the errors
Just a very random example (I literally just clicked through random files): https://github.com/kubernetes/kubernetes/blob/master/pkg/con...
Oh, look, you can't even see the logic behind all the `if err`s which do nothing but return the error to be handled elsewhere.
shric
For me an unrecoverable error is when my program gets into an unexpected state. Given that I didn't anticipate such a thing ever happening, I can no longer reason about what the program will do, so the only sensible course of action is to crash immediately.
ThePhysicist
Do you use recover() a lot? I have never used it much, I guess it is important in some cases but I don't think it's used that much in practice, or is it?
rgallagher27
I've "used" it in pretty much every Go project I've worked on but almost always in the form of an HTTP handle middleware. Write once, maybe update once a year when we have a change to how we report/log errors.
supriyo-biswas
The only use for me has been to put a recoverer middleware[1] to catch any unhandled panics and return HTTP 500s in my applications.
[1] https://github.com/go-chi/chi/blob/master/middleware/recover...
smnscu
Having used Go professionally for over a decade, I can count on one hand the times I used recover(). I've actually just refactored some legacy code last week to remove a panic/recover that was bafflingly used to handle nil values. The only valid use case I can think of is gracefully shutting down a server, but that's usually addressed by some library.
commandersaki
I've seen panic/recover used a lot with recursive descent parsers.
MassiveOwl
I use recover when i'm unsure on how reliable some legacy code is so that we can emit our telemetry and then exit gracefully.
flicaflow
If I remember correctly, the original actor implementation from scala used exceptions for control flow internally. Blocking on input queues would have blocked a whole thread which doesn't scale for a paradigm which should allow you to run very large numbers of actors. So the exception was used to implement something like light threads. Luckily go solves this problem with go-routines internally.
OutOfHere
One of Go's problems, relative to Rust, is that error values of functions can be ignored. In rushed corporate code, this means that developers will inevitably keep ignoring it, leading to brittle code that is not bulletproof at all. This is not an issue in Rust. As for static analyzers, their same use in corporate culture is rare.
Hendrikto
You can ignore errors in Rust too, just like any language. And people do, just like with any language.
> panic and recover are best reserved for exceptional circumstances.
You might go with Joshua Bloch and say exceptions are also best reserved for exceptional circumstances (which actually only means "things aren't working as expected"), that's why Go's authors used "panic" instead of "throw" or something similar, to make clear that it shouldn't be used where you might use exceptions in other languages. I mean, it's in the FAQ too: https://go.dev/doc/faq#exceptions