|
Panicking is, in many ways, the best-case scenario for code that contains a bug. A bug that causes a panic is much easier to find through fuzzing, property testing, even static analysis or analyzing failures in production. It also prevents the bug from "infecting" other code by allowing the program to proceed in an invalid state. If you don't want a panic to take down the whole system, you can isolate the code in a thread and use a supervision tree, or use `catch_unwind` to let the thread perform cleanup and then continue from a known state. Using `slice.get()` and returning `Option` or `Result` for failures that should never happen in correct code is not an improvement. It leads to the same unwinding behavior, as the failure gets passed up the call stack, but with more manually-written code, and more error-handling paths that are hard or impossible to test (because if the program is correct then they are unreachable). It infects calling functions and changes public APIs. Clearly, the best practice is "don't write code with bugs." If your code is bug-free, then it is also panic-free. But we don't really have the tools and techniques to do that all the time in general. The second-best option may be to write code that panics when there is a bug. (People saying "write code that can't panic" are often really just saying "don't write bugs.") (See also "crash-only software," from the Erlang school of reliability engineering.) |
Playing devil's advocate: with unwinding panics (which are necessary for these two approaches), it's harder to make sure all the data structures the thread was using are left in a coherent state. It's not as bad as exception safety in C++, but it does have some similarities. Just take a look at the tricks the Rust standard library uses to keep everything sane even if the stack unwinds (structs implementing Drop normally called SomethingOnDrop, for instance CopyOnDrop), or std::sync::Mutex poisoning.