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.