How to think of unwrap
Short summary
The unwrap
method can be confusing to newcomers. Some advice:
expect(&str)
can be used rather thanunwrap()
to provide context for a panic.- Use
unwrap
andexpect
like assertions. If they panic it's only in unrecoverable situations. - Avoid usage in library code.
- Leave a comment to explain why its okay.
If you read on you'll get more context for the advice, and examples. Even the standard library panics when it could in theory recover.
What is unwrap
?
Rust does not have exceptions. This typically means that fallible functions
return a Result<T>
or Option<T>
to indicate when they have failed.
.unwrap()
allows you to get the Ok
or Some
value inside, if it exists.
Let's look at std::str::from_utf8
from the standard library:
pub fn from_utf8(v: &[u8]) -> Result<&str, Utf8Error>
let s = std::str::from_utf8(b"hello, world").unwrap(); println!("{}", s);
Here we use .unwrap()
in order to get the value inside the Result
. If it
turns out to not be valid UTF-8 (str
has to be UTF-8), then we end up
panicking:
let s = std::str::from_utf8(&[255]).unwrap(); println!("{}", s);
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Utf8Error { valid_up_to: 0, error_len: Some(1) }', src/main.rs:2:45 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
When to use unwrap
So when should you use .unwrap()
in your code?
- When you know better than the compiler.
- When you don't care if some code panics.
- When you have no expectation of recovering from the error.
- In test code.
You know better
Sometimes you know better than the compiler. In our first example we convert the
byte literal b"hello, world"
into a string. We as the programmer can
obviously see that it is valid UTF-8, so the unwrap
will never panic.
When it isn't so obvious (and even when it is), you should leave a comment
explaining why the unwrap
will not panic.
Here's an example from the regex
crate:
fn clear_cache_and_save( &mut self, current_state: Option<&mut StatePtr>, ) -> bool { if self.cache.compiled.is_empty() { // Nothing to clear... return true; } match current_state { None => self.clear_cache(), Some(si) => { let cur = self.state(*si).clone(); if !self.clear_cache() { return false; } // The unwrap is OK because we just cleared the cache and // therefore know that the next state pointer won't exceed // STATE_MAX. *si = self.restore_state(cur).unwrap(); true } } }
The big comment for the unwrap
sticks out quite a bit. It explains why it is
fine in this case, though it may require some understanding of the surrounding
code to understand.
Another time you would use it is for quick and dirty code that maybe you are writing/using once, or don't care about panics. Using it for examples is probably acceptable too.
You should avoid unwrap
when writing library code. Even when
you are certain it is valid now, it may not be with further code changes. At the
very least you should have good test coverage for that area of code. The
performance penalty of handling the failure, using match
or the ?
operator,
is usually small.
If you're worried about the performance impact you should benchmark your code to see how big the impact is. You can make a judgement from there.
No expectation of recovery
There are situations in which you might not want to handle rare or near-impossible edge cases.
Some examples are:
- Mutex poisoning.
- Thread creation failure.
- Allocation failure.
These usually represent serious problems happening elsewhere. Mutex
poisoning
happens when a panic occurs while holding a lock on a mutex (maybe because of a bad
unwrap
!). It's possible to handle this error, but typically code will just call
unwrap
on the result of Mutex::lock()
because there is often nothing
sensible to do, other than propagate the panic and terminate.
There are situations even standard library itself considers beyond scope to handle:
-
std::thread::spawn
panics if the underlying OS fails to make a thread. You need to use theBuilder
to handle this type of failure. -
Box
,Vec
and other allocating code assume memory allocations cannot fail. There are good reasons for this in userspace code, but it has hindered Rust's adoption in some areas, like the Linux kernel or cURL.
In tests
You can think of unwrap
like an assertion. You use it when you are certain
it is fine, or that a situation is unrecoverable if not. This means it is also
quite suitable for using in tests. Here is a test from fastnbt
:
#[test] fn simple_byte() { #[derive(Deserialize)] struct V { abc: i8, } let payload = Builder::new() /* snip */ .build(); let v: V = from_bytes(payload.as_slice()).unwrap(); assert_eq!(v.abc, 123); }
You can see on the from_bytes
line that I just unwrap
the result. If the
result is an error then this is a test failure and it causes a panic, failing
the test like we want.
The expect
method
The
expect
function is like unwrap
but takes a &str
argument allowing the programmer to
explain why this can never fail.
This acts similar to a comment in the source code, and also prints out the message given along with the panic:
thread 'main' panicked at 'should be ascii: Utf8Error { valid_up_to: 0, error_len: Some(1) }', src/main.rs:2:45 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
This can make it a little clearer what has gone wrong. An explanation of why
something cannot fail is usually awkwardly long for a string argument,
and isn't massively useful to the user who had their code panic anyway. So a
longer comment alongside the expect
is a good idea.
Closing
Hopefully this clears up when to use unwrap
and expect
. They are pretty
simple functions, but it can be unclear what idiomatic usage is.
If there are any other areas of Rust that you find trip up newcomers I'd love suggestions for further articles. You can find me on twitter @gagetheory.