It\'s easy to crash at runtime with unwrap
:
fn main() {
c().unwrap();
}
fn c() -> Option {
None
}
Resul
While the whole “error handling”-topic is very complicated and often opinion based, this question can actually be answered here, because Rust has rather narrow philosophy. That is:
panic!
for programming errors (“bugs”)Result<T, E>
and Option<T>
for expected and recoverable errorsOne can think of unwrap()
as converting between those two kinds of errors (it is converting a recoverable error into a panic!()
). When you write unwrap()
in your program, you are saying:
At this point, a
None
/Err(_)
value is a programming error and the program is unable to recover from it.
For example, say you are working with a HashMap
and want to insert a value which you may want to mutate later:
age_map.insert("peter", 21);
// ...
if /* some condition */ {
*age_map.get_mut("peter").unwrap() += 1;
}
Here we use the unwrap()
, because we can be sure that the key holds a value. It would be a programming error if it didn't and even more important: it's not really recoverable. What would you do when at that point there is no value with the key "peter"
? Try inserting it again ... ?
But as you may know, there is a beautiful entry API for the maps in Rust's standard library. With that API you can avoid all those unwrap()
s. And this applies to pretty much all situations: you can very often restructure your code to avoid the unwrap()
! Only in a very few situation there is no way around it. But then it's OK to use it, if you want to signal: at this point, it would be a programming bug.
There has been a recent, fairly popular blog post on the topic of “error handling” whose conclusion is similar to Rust's philosophy. It's rather long but worth reading: “The Error Model”. Here is my try on summarizing the article in relation to this question:
In summary: use unwrap()
when you are sure that the recoverable error that you get is in fact unrecoverable at that point. Bonus points for explaining “why?” in a comment above the affected line ;-)
In other words, can I say my program is reliable if I use unwrap? Or must I avoid unwrap even if the case seems simple?
I think using unwrap
judiciously is something you have to learn to handle, it can't just be avoided.
My rhetorical question barrage would be:
(1) is like unwrap, indexing panics if you make a contract violation and try to index out of bounds. This would be a bug in the program, but it doesn't catch as much attention as a call to unwrap
.
(2) is like unwrap, integer division panics if the divisor is zero.
(3) is unlike unwrap, addition does not check for overflow in release builds, so it may silently result in wraparound and logical errors.
Of course, there are strategies for handling all of these without leaving panicky cases in the code, but many programs simply use for example bounds checking as it is.
unwrap()
is not necessarily dangerous. Just like with unreachable!()
there are cases where you can be sure some condition will not be triggered.
Functions returning Option
or Result
are sometimes just suited to a wider range of conditions, but due to how your program is structured those cases might never occur.
For example: when you create an iterator from a Vec
tor you buid yourself, you know its exact length and can be sure how long invoking next()
on it returns a Some<T>
(and you can safely unwrap()
it).
unwrap is great for prototyping, but not safe for production. Once you are done with your initial design you go back and replace unwrap()
with Result<Value, ErrorType>
.
There are two questions folded into one here:
panic!
acceptable in productionunwrap
acceptable in productionpanic!
is a tool that is used, in Rust, to signal irrecoverable situations/violated assumptions. It can be used to either crash a program that cannot possibly continue in the face of this failure (for example, OOM situation) or to work around the compiler knowing it cannot be executed (at the moment).
unwrap
is a convenience, that is best avoided in production. The problem about unwrap
is that it does not state which assumption was violated, it is better instead to use expect("")
which is functionally equivalent but will also give a clue as to what went wrong (without opening the source code).