Hacker News new | ask | show | jobs
by dietrichepp 1261 days ago
One of the difficulties here is that you can’t actually turn every fault into an error return. Yes, Rust “partially” resolves the issue, but if it’s not null pointers, it’s out of bounds array accesses or something like that.

The reason I say you can’t turn every fault into an error return is because an unexpected infinite loop is also a type of fault. If you call some function that has bugs in it, then there’s a chance that the function won’t return. “Won’t return” may mean that it panics, deadlocks, loops forever, or loops for so long it might as well be forever. In Haskell, all of these different behaviors are lumped in together as “bottom”, and bottom is a value which has well-defined semantics even though it covers all these different cases. There’s a whole debate in the Haskell community about whether functions should be total. A total function doesn’t return bottom unless bottom was an argument—in Go/Rust terms, a total function does not panic and does not infinitely loop.

My take—as long as you think “this function might not return because it has a bug in it”, there’s not a good reason to prohibit panic(). The panic() functionality is a more controlled, flexible way for a function to not return.

I think you could write a whole article on when to use recover() in Go. The idea that you should never panic is a bit of a hopeless dream—if that’s the kind of correctness you want, then it sounds like you want some kind of formal verification, which can be done but not in Go. The idea that you should never recover() is too severe. Yes, you can find code that leaves your program in an inconsistent state after a panic(), but in practice I’d say that these problems are relatively rare. You can also use panic/recover to simplify your code in certain ways. I’ve used panic/recover to write parsers or deserialization code, where you just use a panic() to return an error from the top-level parser/deserializer, which catches it with a recover().

I’d also say that it’s relatively normal to recover() inside your request handler for network services. You could weigh the risk of panic/recover leaving your application server in an unexpected state against the risk of getting a denial of service from panic taking down the whole app.

2 comments

Panics in Rust are for logic errors that can't be meaningfully recovered from. You can easily convert an error state into a panic, by using .unwrap() or .expect(), or pass the error back to the caller via the '?' syntax. Rust does have a "recover"-like facility that can catch a panic, but it's intended for exceptional use; it also has no effect when panics are configured (at the whole-program level) to abort the program immediately.
Yes, that matches my understanding. In Go, panics can also be used for recoverable errors, if you so choose. In Rust you can catch_unwind() which is similar to Go’s recover(), and the big difference is that (1) it may not work depending on how your project is configured, and (2) its use is very strongly discouraged.
In theory, Go is the same way, but Go does a lot less to prevent panics, limit them to very specific circumstances, mitigate their effects, or limit unexpected failure modes.
> Yes, Rust “partially” resolves the issue

Which is a pretty big advantage!

Doesn’t Go also partially solve the issue?

I write both Go and Rust code, and I don’t feel like there’s a huge advantage to one side or the other here. Maybe I don’t understand what point you are making.

Whether it's a big or small advantage is an empirical question. How often do panics happen otherwise, and how bad are they? It's going to depend on the system.