Completely different. In Rust when you spawn a thread [1] or an async task [2], you'll get a handle from which you can check if a panic happens inside the thread/task. Even if you ignore the handle, a panic inside a thread/task won't crash the entire program.
---
It did not though?
- threads implicitly catch_panic at their boundary, and convert that to a `Result` which you can receive when you join on the thread: https://doc.rust-lang.org/std/thread/type.Result.html
- in theory an async runtime could shut down on panic, but tokio at least does not, much like `std::thread::spawn` it'll catch_panic and return a JoinError to however is await-ing the joinhandle: https://docs.rs/tokio/latest/tokio/task/struct.JoinError.htm...
If you drop the handles, the error just is not signaled:
- sync: https://play.rust-lang.org/?version=stable&mode=debug&editio...
- async (tokio): https://play.rust-lang.org/?version=stable&mode=debug&editio...
But I do prefer return values over exceptions! I have not used Go and I think the err != nil boilerplate might drive me a little insane, but Haskells option types seem a nice fit.
So panic is supposed to be “I’m not sure what to do now, I don’t care if my caller knows”?
No process running outside of kernel space ever has any business panicking. Throw an error and move on with your life (or don’t, that’s fine too) but don’t punish your caller by forcing it to crash because you failed to foresee X, Y, or Z circumstances.
For that matter, how is the called method supposed to determine what the state of the world even is? And why on Earth would we trust it to do so? Is encapsulation not even a thing anymore? I really just don’t understand.
- Accessing array out of bounds: panic
- File not found: error
func (t token) symbol() byte {
switch t {
case add: return '+'
case sub: return '-'
case mul: return '*'
case quo: return '/'
default: panic("unknown token")
}
}
Errors are different. For example, url.Parse returns an error if its input string is an invalidly formatted URL. package url
func Parse(rawURL string) (*URL, error)error values are meant to represent expected, unsuccessful conditions—internal properties of the program—during the program's execution. for example, looking up the address for a domain name is an operation that can be expected to fail at times, and an error is appropriate here.
errors in go are not meant to represent properties external to the program, such as mistakes by the package author or documented incorrect usage of the package by the package user. in go these are panics.
perhaps other languages use exceptions for both but that conflates.
No, absolutely not!
Panics are the result of conditions that absolutely should crash the program! They are not errors, they are not similar to exceptions, they are conditions that should not occur, and thus if they do occur, either the program itself, or the platform it is running on, has a problem that needs to be solved before the program runs again.
If a cars fuel is running low, it should turn on a warning light and inform the driver that he should find a gas station. If a piece of the road is suddenly missing, the car needs to stop.
If a panic occurs, I don't WANT the program to continue executing! That would lead me right back into C land, where an out-of-bounds memory access would silently start to poison my program and crash it days after the actual error ocurred, if i'm lucky! If I'm unlucky, and the process needs to be sufficiently privileged, it would kill the server, again several days (or weeks) later, with slim-to-no chance for me to figure out what even went wrong.
> If a piece of the road is suddenly missing, the car needs to stop.
Uh, no. If a piece of the road is missing, it’s up to the driver to stop, not the car. The car has no business whatsoever determining whether the road exists or not, except inasmuch as it communicates to the driver “hey, whatever the heck this is is difficult or impossible to drive on, your call what we do though.”
Then you're looking for resumable conditions, which just so happen to be effectively isomorphic to async-await. (The called function is effectively a piece of async code which 'awaits' upon a failure condition, after which it can either be resumed or canceled as with an ordinary panic.)
And most of the time, this is EXACTLY what happens. In fact most libraries that do use panics, recover them internally and return errors to the callers.
Again: panics are not errors, panics are not exceptions. A library should never panic in a way that it's caller notices, UNLESS it met a condition that simply is not recoverable, not by the library itself, and not by it's caller.
Yes, this requires care by the libraries authors not to overuse panics. Yes, there are libraries that do overuse them. No, this doesn't change anything about what I wrote. If a library I use would do that, I would either fix the library, or find a better one.
> Uh, no. If a piece of the road is missing
Okay, let me try again with a better analogy:
If a cars engine breaks down, the car will stop. It doesn't matter if the driver wants the car to go for another 100 kilometers. It stops. Period. Not because anyone or anything "wants" the car to stop, but because its engine "package" met a condition, under which the further running of whatever context it is used in (in this case, the car), is no longer feasible and/or possible.
That is exactly what panicking in a way that hits the runtime communicates: Oh noes, the engine just broke down, and now the machine can no longer run.
A library that unrecoverably crashes your program for any reason is defective by design, and a programming language that purposely allows a library to do so as a design choice is also defective by design in my opinion.
The one option he doesn't have though: Continue running the car as-is. And that's the point I am making.
> and a programming language that purposely allows a library to do so as a design choice is also defective by design in my opinion.
You are entitled to your opinion of course, but I'm not going to agree, and it seems a lot of people are completely fine with that design decision: https://madnight.github.io/githut/#/pull_requests/2023/3
Again, the library isn't "crashing the program". The library meets a condition under which it can no longer function, no matter what the caller does, and can no longer function in a way that makes running the entire program infeasible. That is the point of panicking and not recovering it within the library.
And yes that is the libraries call to make. Yes, it needs to have a very good reason to make that call. Very few libraries panic in that way. And the ones that do, and are widely used, have very good reasons to. If libraries do so without a good reason, then people stop using them.
Exactly! So raise an EngineFailure exception and let the driver decide how to proceed. But don’t kill the driver because you can’t figure out how anyone could possibly recover from what seems to you like the end of the world, when it might just be a speed bump as far as the driver is concerned.
> You are entitled to your opinion of course, but I'm not going to agree, and it seems a lot of people are completely fine with that design decision: https://madnight.github.io/githut/#/pull_requests/2023/3
I mean, a lot of people are fine with programming in PHP, that doesn’t make it a good choice.
> And yes that is the libraries call to make.
No. Absolutely, 100%, beyond question not. The library can’t possibly know every conceivable circumstance in which it is called, and therefore it is a guarantee that there is some conceivable set of circumstances from which what appears to be a fatal error from the library’s perspective is nothing more than a blip, or even a complete non-issue, from the perspective of the caller. Panicking the calling program so it can’t possibly continue is an abuse of trust, and any library that does so shouldn’t be admitted to any repo, and any language that allows it to do so is broken by design and should be considered harmful.
In the analogy, the car is the program. The ENTIRE program. It is the limit of the analogy. The car driving is the program running. The car no longer being able to drive, is the program not running. Everything outside of that is out of the scope of the analogy.
> I mean, a lot of people are fine with programming in PHP, that doesn’t make it a good choice.
That is absolutely true, but it doesn't make PHP "defective". If someone is unhappy with how Go's `panic` works, they can use something else, same as I am not going to use PHP.
> No. Absolutely, 100%, beyond question not.
Yes, absolutely, 100%, beyond question it is.
The library is an implementation hidden behind a public interface. Same as I don't have to worry about how it performs it's functions, I don't have to worry how it determines what it considers to be an irrecoverable failure condition.
If I disagree with a libraries decision on that, I have the same options I have if I disagree with how it's interface works (assuming it is open source): 1) I can fork it and roll my own implementation. 2) I can vendor-copy it and adapt it locally for the scope of that project. 3) I can use another lib (including writing my own from scratch).
> The library can’t possibly know every conceivable circumstance in which it is called
It doesn't have to. If the engine breaks down, it does't matter if the car is yellow, the sun is up, or the driver was born on a Tuesday. The engine is broken, and cannot run. End of story.
If there are conceivable circumstances under which the lib should continue running instead of panicking, it is the libs job to implement it, same as it is its decison what these circumstances are, and when they apply.
> and any language that allows it to do so is broken by design and should be considered harmful.
Well, I think we are gonna have to agree to disagree on this. Considering Go's success and continuous growth, it is pretty safe to say that many people and industries don't consider it broken, nor harmful.
A quick google turns up https://go.dev/blog/defer-panic-and-recover, which says:
For a real-world example of panic and recover, see the json package from the Go standard library. It encodes an interface with a set of recursive functions. If an error occurs when traversing the value, panic is called to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value
That's panic and recover used for control flow, just like might use exceptions in other languages.
Looks like panic has at least two purposes, depending on whether you recover or not?
And another quick googling turns up this:
https://github.com/golang/go/issues/26799
which says:
However, this is a poor example and a misuse of panic and recover as exceptions.
See https://golang.org/doc/effective_go.html#panic
The example will be invalid soon with the release of go 1.11 as well. The
panic/recover was removed from the unmarshal code. See master's
So no, panic and recover are not meant to be used as control flow. Yes, they CAN be used that way. I also CAN use pythons `eval` a lot, use a scripting language as my command line interpreter, or make an apple-pie with onions and garlic.As for why `recover` exists in the first place: Packages should have the ability to stop internal panics. There are various reasons for that, some worse than other. For example it is probably okay if a package doing sensor readouts recovers from a division by zero and replaces it with an error saying that the sensor value makes no sense.