3 ms·
Simple indeed. As such, it has "missing parts" that I think are important: 1. Reasons to intentionally panic are mainly for invariants that you failed to expr
by dureuill 5y ago
Simple indeed.
As such, it has "missing parts" that I think are important:
1. Reasons to intentionally panic are mainly for invariants that you failed to express through the type system, and so that have to be checked at runtime, although you "know" that they should always be true. An example of this is having a struct implementing Drop while still having a method of that struct that allows to take its internals. To achieve this you typically need to put the internals in an Option, and have drop only drop if the option is there. Since all methods can only be called when the object has its internal in place, you should unwrap the internals before using them in each method. Returning an error in this case would be exposing internal architecture details and essentially "dead code".
2. You can recover from a panic. This is especially useful if one of your dependencies is misusing panics or you're triggering something that "should never happen". You may want to use that for a longer-lived application (eg a web server), so that a panic in one connection does not bring down the entire server. This is also necessary before calling into FFI because unwinding in another language is UB at this point.
3. When writing applications, `anyhow` is a great crate for "stringly typed, user-facing" errors. Its superpowers are to convert any error into its erased error type, and to be able to add context as a String to any error. You can then iterate through the various layers of errors, which is great for giving context to the end-user of the application. You shouldn't use anyhow in libraries though, because otherwise you will expose "stringly typed" errors everywhere, that are hard to act upon for your programmatic users.
Error handling is a difficult topic in any language. Rust's error handling story is better than in many languages, but I feel it still lacks some things:
- I would like to be able to mix and match error types without making them "stringly typed" and more freely than with the rigid "from" implementations using thiserror
- The current system pushes people to make a single big Error enum for their crate, which obfuscates which errors can or cannot be returned by each individual function, and so makes it harder to act upon each error condition. I try to 1. have one specialized enum per function (or per functions with similar errors) 2. have one "generic" enum containing all of the others, so that if the user doesn't care about the specific error they can
just `.into` their way to the generic error.
- The current system is very "type" oriented, and so doesn't push people to actually model "error conditions". eg multiple error conditions can be caused by IoErrors, but it is tempting to snuggle all IoErrors in a single enum variant "IoError(IoError)", whereas a more expressive way would be to describe error conditions: eg "CouldNotOpenConfig(IoError)" and "CouldNotSaveData(IoError)" instead of a single "IoError(IoError)".
The article remains a good short introduction to error handling in rust, but I wonder if we *want* a short introduction to a difficult topic.