In Which one should I use? panic! vs. unreachable! I read that unreachable! is preferred over panic! if the code can guarantee that the statement will never be reached. Now I am wondering if unreachable! would also be preferred over unwrap() or expect(...) since those are also just wrappers around panic!.
So given the following (stupidly) simple example:
let something = Some(5);
let number = something.unwrap();
Since in this situation I can guarantee that something can never be None should I not use unwrap and instead use the slightly more complicated unreachable! version?
let something = Some(5);
let number = something.unwrap_or_else(|| unreachable!());
Thank you for your quick answer. In this case I have a quick follow up question:
If I have a method that returns a Result<O, E> where the error E has multiple variants and my calling code can guarantee that none of those variants will trigger, should I just use unwrap/expect or do a match and use unreachable! or something like that for the error variants.
My reasoning is that in this case I thought through all possible error variants and none can trigger, but what if at some point there is a breaking change and this function now has a new error variant. If I would have used the match the compiler would remind me (force me) to recheck this new error variant for the calling code.
Other panic shorthands include todo!() -> panic!("not yet implemented") and unimplemented!() -> panic!("not implemented") (and the various asserts are just conditional panics)
panic!() itself is just a call to std::panic::panic_any() with a String (or directly with the single argument before edition 2021)
In this specific case, and supposing I can't remove or delay the choice to put the value inside an Option, then I'd use expect:
let something = Some(5);
let number = something.expect("constant value in `something` is statically not None");
That way, if and when the assumption is invalidated, there's a distinct message both helping locate the mistake, and helping explain what I was thinking when I wrote the code.
Your proposed expect touches another question we are discussing currently internally.
Should we design expect messages with the end user in mind (that would see this message if it ever would trigger) or with the developer in mind. Basically the argumentation is that if we design it for the end user it does not make sense to reference local/private variables since they are not visible to the user. On the other hand, I saw this in a lot of projects that developers just put their reasoning in the expect string and focus only on the developer reading it, not so much the end user.
So the question for the simple example above would be should we expose internal details like the variable something in this message or should this just be a comment for the developer, i.e.
let number = something.expect("constant value in `something` is statically not None");
versus
let number = something.unwrap(); // constant value in `something` is statically not None
I also brought up the argument "distinct message to locate the mistake" but in the end unwrap() also prints a file path and a line number which should be enough to pinpoint the problem (except if you have multiple unwraps in one line which I would avoid of course).
Personally, I use the approach that if a user can see a panic then everything's already gone terribly wrong and your best hope is to get something useful on screen for them to be able to send you (even if actually getting them to do so is tough).
You might be able to do something clever with std::panic::set_hook() to provide a generally nicer experience, but you're already in a somewhat uncertain state, so it's probably a bad idea to eg send network requests or anything like that.
This example is indeed suboptimal to showcase your point.
let something = Some(5);
let number = something.unwrap();
If something is guaranteed to have a valid value, why is it an Option<T> and not T?.
If you cannot guarantee, that any Option<T> is never None, but you accept that the program crashes if it is, e.g. because a documented contract was not upheld, you should imho .expect() the value of the Option<T> with an appropriate message, that the contract was not upheld.
And if you do want to reliably do something on panic, isolate the panicking parts (which possibly includes your entire program minus main) into a separate thread so you get a nice Result::Err on join if the thread panics. Rust’s safety guarantees make it fairly certain that a thread gone astray cannot poison other threads’ state, unless unsafe is involved (heap corruption is probably the most likely way for it to happen).
For ultimate reliability, you’d spawn a whole new process to do the work and communicate failure info to the parent via stderr.
Thanks for your answer, seems like it didn't solve our discussion though haha, we interpret your answer differently. So do you say you would put the reason (even if it contains developer-level information that user wouldn't understand) inside the str passed to expect, i.e.
let number = something.expect("constant value in `something` is statically not None");
or would you keep it as a developer comment and rely on the file path + line number in the panic message, i.e.
let number = something.unwrap(); // constant value in `something` is statically not None
The practice as of today is that, if a panic occurs, then it is generally regarded as a bug. The extent to which you want to make those panic messages "user-friendly" depends on your own taste. But I'd say the primary force driving the behavior of a panic message getting dumped to stderr is to aide a developer in diagnosing the root cause of the bug and fixing it.
How to balance what goes in expect versus not using expect versus adding comments explaining things is really a judgment call. Many folks use expect as a sign-post indicating just that some thought has gone into it and that it is intentional. That is, as a way of differentiating between an intentional panicking branch versus a thoughtless one.
I'll stop there because my blog elaborates much more on these points and should give you a general working philosophy of how to approach this sort of thing.