This is a strong opinion, loosely held:
panic!
means that something the program author(s) believed impossible has happened. This makes abort-on-panic reasonable - something believed to be impossible actually happened, so all assumptions are suspect - and implies that the only way to prevent the panic
happening again is to revisit the program and fix the bug. In this view, catch_unwind
's use case is to avoid aborting the whole program when a bad assumption is found.
Result
means that an error condition can happen, and that you want something elsewhere to handle that error. It might turn into a panic!
(via unwrap
or expect
), it might result in a HTTP 500 or HTTP 400 error to the client, depending on the error, it might trigger use of an alternative code path. At the point of returning a Result::Err
, you don't care how it's handled, you just want someone else to make the decision about what's broken.
In general, reach for Result
if the error is caused by input (e.g. the client sending an invalid request) or if the error is one that the caller might recover from in some way. Use panic!
only if the route to panicking code should never be possible in your opinion.
So, for example:
/// Threshold below which we use the default - *must* be greater than 0
const THRESHOLD: i64 = 128;
fn foo(in: i64, default: u64) -> u64 {
if in < THRESHOLD {
return default;
}
in.try_into().unwrap()
}
is fine to panic in unwrap()
, because as a programmer, I believe that it is impossible to find a value of in
that is greater than or equal to 128, but does not fit in u64
. If I'm wrong, or if someone later changes THRESHOLD
to be negative, that's a bug that needs a recompile to fix the program.
On the other hand:
fn bar(in: i64, threshold: i64, default: u64) -> Result<u64, TryFromIntError> {
if in < threshold {
return default;
}
in.try_into()
}
cannot use unwrap
sensibly; it is possible for the supplied parameter threshold
to be negative, and the next layer out should make the decision about what to do here.