38 ms·
The cost of Go's panic and recover
- deleted 2y ago[deleted]
- MassiveOwl 2y agoI use recover when i'm unsure on how reliable some legacy code is so that we can emit our telemetry and then exit gracefully.
- jjmarr 2y agoI'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 2y agoEnterprise ready. :)
- CJefferson 2y agoHonestly, this is the best way to write recursive algorithms in my opinion (and I write a lot of recursive search algorithms in my research). The alternative is every single function has to return a boolean, along with whatever else it would return, which is true when you have found a solution, and you then return straight away -- effectively just reimplementing exceptions by hand, which doesn't feel like a useful use of my time.
- kamaal 2y agoIts strange that its 2025 and we haven't compiled recursion as a design pattern with rules, like a framework, using which all problems that can be solved by recursion can be represented.
- rollcat 2y agoSounds like Go to me! Every single function that can fail has to return an error value, which is nil if you have succeeded. I love Go, but its error handling leaves so much to be desired. I even have an Emacs macro that inserts "if err != nil" for me. Also very easy to make a mistake in the rare case where you have to write "if err == nil"; my eyes just skip over any line that includes the words "if", "err", and "nil", as if it was some attention-hungry inline ad. Rust started off chatty, but soon introduced the "?" syntax, which works perfectly well in the 99% case. Go had a couple of similar proposals, but "keep if err != nil" just... won.
- 9rx 2y ago> which works perfectly well in the 99% case. Not without its related traits and whatnot. Without those you have the same all the same problems the Go proposals keep running into. Trouble is that it is not yet clear what the Go equivalent is to those related features. However, the attempts to figure that out keep coming. Ian Lance Taylor (of generics fame) recently implemented the latest proposal to try it out, although it seems it too has failed, but suggests that things are inching closer to something usable when the initial details were able stand up to basic scrutiny. Nothing has "won" yet.
- stouset 2y agoI remember when Rob Pike wrote an essay insisting that `if err != nil` isn’t all that common and golang programmers clearly would wrap that logic in more fluid interfaces which would prevent the redundant boilerplate.
- DangitBobby 2y agohttps://go.dev/blog/errors-are-values https://go.dev/blog/errors-are-values
- the_gipsy 2y agoThis reads like satire!
- kccqzy 2y agoThat's why in Haskell after the Either monad becomes popular, people simply made a library that flips the arguments to become the Success monad. The problem with most languages here is the name "exceptions" implying it's for exceptional scenarios, but without any substitute for good non-local control flow.
- layer8 2y agoIt’s not necessarily only for exceptional scenarios, but instead it is an exception to the normal return of a value. The usage where the return is always by exception (as in the n-queens example) is itself an exception ;). I do think it’s useful to mark one (return) type as the primary/normal/good case and other types as secondary/auxiliary/error cases, and have the latter auto-escalate independently.
- tcfhgj 2y ago"The alternative is every single function has to return a boolean" Rust: Return Some(value) or None
- CJefferson 2y agoI tried that, the problem was some functions wanted to return an Option, then you end up with Option<Option<T>>, which is technically fine, but I find a bit mind bending.
- vbezhenar 2y agoAnother alternative is setjmp/longjmp.
- flicaflow 2y agoIf 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.
- ThePhysicist 2y agoDo 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?
- supriyo-biswas 2y agoThe 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/recoverer.go https://github.com/go-chi/chi/blob/master/middleware/recover...
- smnscu 2y agoHaving 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 2y agoI've seen panic/recover used a lot with recursive descent parsers.
- rgallagher27 2y agoI'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.
- colonial 2y agoAt least in Rust (which has an effectively identical API here) the only reasonable use case I've seen is as a "last resort" in long-running programs to transform panicking requests into a HTTP 500 or equivalent.
- lelandbatey 2y agoI've only ever had to use it in certain complex cleanup mechanisms (certain transaction handling, request middleware, etc)
- shric 2y agoI 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".
- troupo 2y agoIn 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 2y agoGo 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 2y ago> 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/controlplane/controller/clusterauthenticationtrust/cluster_authentication_trust_controller.go#L143 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.
- rob74 2y ago> 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 https://go.dev/doc/faq#exceptions
- 9rx 2y ago> 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.
- bazoom42 2y agoTerminology 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.
- tored 2y agoIsn't that what we have exception hierarchies for?
- 9rx 2y ago> A crashed harddisk is clearly an exceptional circumstance. It is clearly not. It is very much something to anticipate. At sufficient scale, it is guaranteed that it will happen. There is nothing exceptional about it. > More specific terms is needed to distinguish errors in the code (eg divide by zero) from unpreventable errors like network failure. Luckily we have such terminology already: Exceptions (caused by mistakes in the code) and errors (caused by external faults).
- OutOfHere 2y agoOne 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 sane use in corporate culture is rare.
- Hendrikto 2y agoYou can ignore errors in Rust too, just like any language. And people do, just like with any language.
- treyd 2y agoIn Rust you have to explicitly state that you're ignoring the error. There is no way to get the value of an Ok result without doing something to handle the error case, even if that just means panicking, you still have to do that explicitly. In Go you can just ignore it and move on with the zeroed result value. Even the error the compiler gives with unused variables doesn't help since it's likely you've already used the err variable elsewhere in the function.
- foobarbaz33 2y agoTrue, but running errcheck will catch cases where you accidentally ignore the error. Maybe not as good as having it built-in to the language like Rust, but the goal of error check safety is achieved either way. And there's a few cases like Print() where errors are so commonly ignored you don't even want to use the "_" ignore syntax. Go gives you the poetic license to avoid spamming underscores everywhere. error linters can be configured to handle a variety of strictness. For non-critical software, it's OK to YOLO your Print(). For human-death-on-failure software you may enforce 100% handling, not even allowing explicit ignore "_" (ie become even stricter than Rust language default)
- sapiogram 2y agoerrcheck will not catch everything. For example, it will not stop you from using a value before checking the error, as long as you check the error later. I've personally broken things in production because of that corner case, despite (usually) being very careful about error handling.
- L-four 2y agoWriting transaction code wrapping a user provided function which can error or panic is a real pain to get right. If the user code panics there is no error if the user code succeeded there is no error. So you have to call recover on success.
- enjoylife 2y agoOnly at the very end does the article call out there is actually a performance aspect if you use panic and recover as intended. > So it seems that panic and recover can be beneficial to performance in at least some situations. Namely top level & centralized recovery handling. I'll also point out its important your panic recovery must happen in a deferred function if your kicking off new goroutines. For example, your server library probably has default panic recovery on the goroutines handling inbound requests, but any new goroutines you create as part of handling the request (e.g. to parallelize work), will not have this handling built in. See https://go.dev/blog/defer-panic-and-recover https://go.dev/blog/defer-panic-and-recover for more.
- peterohler 2y agoMy experience is quite a bit different. Of course the examples I would use are more like what you might expect in real code. The comparison should be against code that calls a function that either returns and error and checks that error or one that panics and recovers. The overhead of returning the extra error and then the conditional used to check that error is more than a panic on error and recovery somewhere up the stack. This was not true in the early days of go but it is true today. It really depends on the code being written. Try one approach then the other and see if it works better in your situation. For the example in the article there is really no need for an error check in the idiomatic case so why compare that to using panic. If there was an error to check the result would be much different.
- donatj 2y agoI write a pretty significant amount of Go code for my day job, and I have written code that calls panic probably less than five times. There are only really two types of cases where I would even consider it an option. Firstly, cases where I am handling an error that should never ever happen AND it is the only error case of the function call such that eliminating it removes the need for an error on the return. The other case is where I have an existing interface without an error return I need to meet and I have a potential error. This is the result of bad interface design in my opinion but sometimes you have to do what you have to do,
- hanikesn 2y agoHow often did you access a nil pointer and caused a panic?
- KingOfCoders 2y agoNot a lot, nilaway works quite well.
- KingOfCoders 2y agohttps://github.com/uber-go/nilaway https://github.com/uber-go/nilaway
- donatj 2y agoWell I meant code that panics intentionally, via a call to panic, I've certainly written my share of off-by-ones ;)
- KingOfCoders 2y agoI only use panic during startup on unrecoverable errors, can't read credentials for example.
- __turbobrew__ 2y agoHere are my rules of thumb: 1. Only panic in the top level main() function. Bubble up all errors from sub functions and libraries to the top level function and then decide what to do from there. 2. If you want to offer a library function which can panic on errors, create two versions of the function: one which returns an error, and one which panics on an error and has a name which starts with ’Must’. For example Load() returns an error and MustLoad() doesn’t return an error and instead panics on error.