1

Since using unwrap may be problematic because it crashes in the error scenario, it may be considered as dangerous usage.

What if I am hundred percent sure that it will not crash, like in the following scenarios:

if option.is_some() {
    let value = option.unwrap();
}
if result.is_ok() {
    let result_value = result.unwrap();
}

Since we already checked the Result and Option there will be no crash with the unwrap() usage. However, we could have used match or if let. In my opinion, either match or if let usage is more elegant.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Akiner Alkan
  • 6,145
  • 3
  • 32
  • 68
  • This question does not have an answer that is almost entirely based on opinion, so I've voted to re-open it. Idiomatic Rust code only uses unwrap where its failure would otherwise indicate a bug in the code. This is fairly easy to support by doing a survey of the ecosystem. – BurntSushi5 Dec 26 '18 at 14:08

2 Answers2

4

Let's focus on Result; I'll go back to Option at the end.

The purpose of Result is to signal a result which may succeed or fail with an error. As such, any use of it should fall into this category. Let's ignore the cases where a crate returns Result for impossible-to-fail operations.

By doing what you are doing (checking if result.is_ok() then extracting the value), you're effectively doing the same thing twice. The first time, you're inspecting the content of the Result, and the second, you're checking and extracting unsafely.

This could indeed have been done with a match or map, and both would have been more idiomatic than an if. Consider this case for a moment:

You have an object implementing the following trait:

use std::io::{Error, ErrorKind};

trait Worker {
    fn hours_left(&self) -> Result<u8, Error>;
    fn allocate_hours(&mut self, hours: u8) -> Result<u8, Error>;
}

We're going to assume hours_left() does exactly what it says on the tin. We'll also assume we have a mutable borrow of Worker. Let's implement allocate_hours().

In order to do so, we'll obviously need to check if our worker has extra hours left over to allocate. You could write it similar to yours:

fn allocate_hours(&mut self, hours: u8) {
    let hours_left = self.hours_left();
    if (hours_left.is_ok()) {
        let remaining_hours = hours_left.unwrap();
        if (remaining_hours < hours) {
            return Err(Error::new(ErrorKind::NotFound, "Not enough hours left"));
        }
    // Do the actual operation and return
    } else {
        return hours_left;
    }
}

However, this implementation is both clunky and inefficient. We can simplify this by avoiding unwrap and if statements altogether.

fn allocate_hours(&mut self, hours: u8) -> Result<u8, Error> {
    self.hours_left()
        .and_then(|hours_left| {
            // We are certain that our worker is actually there to receive hours
            // but we are not sure if he has enough hours. Check.
            match hours_left {
                x if x >= hours => Ok(x),
                _ => Err(Error::new(ErrorKind::NotFound, "Not enough hours")),
            }
        })
        .map(|hours_left| {
            // At this point we are sure the worker has enough hours.
            // Do the operations
        })
}

We've killed multiple birds with one stone here. We've made our code more readable, easier to follow and we've removed a whole bunch of repeated operations. This is also beginning to look like Rust and less like PHP ;-)

Option is similar and supports the same operations. If you want to process the content of either Option or Result and branch accordingly, and you're using unwrap, there are so many pitfalls you'll inevitably fall into when you forget you unwrapped something.

There are genuine cases where your program should barf out. For those, consider expect(&str) as opposed to unwrap()

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sébastien Renauld
  • 19,203
  • 2
  • 46
  • 66
  • Okay, I am thinking in the same manner. So by looking that perspective I do not see any advantages the usage of `unwrap` in any cases? Maybe only at quick test codes it may be usefull? – Akiner Alkan Dec 25 '18 at 10:37
  • 1
    @AkinerAlkan there's a reason `unwrap()` is widely considered a code smell in rust. The only time you may have a justification for it is if you're consuming a `Result` but your function's signature does not have one - at which point you either need to consider 1) why the overarching signature had a `Result`, or 2) why yours does not – Sébastien Renauld Dec 25 '18 at 10:39
  • 3
    unwrap is most certainly not widely considered a code smell. – BurntSushi5 Dec 25 '18 at 12:17
  • 1
    @BurntSushi5 [the doc](https://doc.rust-lang.org/1.5.0/book/error-handling.html#a-brief-interlude-unwrapping-isnt-evil) has a section on this, with two (albeit opinionated) edge cases where `unwrap` can be judicious: in hackish code, or when you *cannot recover* from the failure (and where the panic is justified). The OP is very clearly in neither of those two cases, I'm sure we can agree on that. – Sébastien Renauld Dec 25 '18 at 13:07
  • 2
    Yes, I know what that doc says. I wrote it. The point of that section in the docs is to specially say that unwrapping is okay in some circumstances. I am specifically taking issue with your claim that it is widely regarded as a code smell. But it isn't. It's only a code smell when it's used for error handling, which is an important qualification. Also, the doc doesn't say "it's okay when you cannot recover," which is less actionable. Instead, it's easier to just say that if end users observe a panic, then there exists a bug somewhere in the code. – BurntSushi5 Dec 25 '18 at 14:11
  • OP doesn't give a very specific scenario, so it's hard to say concretely what the answer is. It seems like a broad question which probably deserves a broad answer. – BurntSushi5 Dec 25 '18 at 14:12
3

In many, many cases you can avoid unwrap and others by more elegant means. However, I think there are situations where it is the correct solution to unwrap.

For example, many methods in Iterator return an Option. Let us assume that you have a nonempty slice (known to be nonempty by invariants) and you want to obtain the maximum, you could do the following:

assert!(!slice.empty()); // known to be nonempty by invariants
do_stuff_with_maximum(slice.iter().max().unwrap());

There are probably several opinions regarding this, but I would argue that using unwrap in the above scenario is perfectly fine - in the presence of the preceeding assert!.

My guideline is: If the parameters I am dealing with are all coming from my own code, not interfacing with 3rd party code, possibly assert!ing invariants, I am fine with unwrap. As soon as I am the slightest bit unsure, I resort to if, match, map and others.

Note that there is also expect which is basically an "unwrap with a comment printed in the error case". However, I have found this to be not-really-ergonomic. Moreover, I found the backtraces a bit hard to read if unwrap fails. Thus, I currently use a macro verify! whose sole argument is an Option or Result and that checks that the value is unwrapable. It is implemented like this:

pub trait TVerifiableByVerifyMacro {
    fn is_verify_true(&self) -> bool;
}
impl<T> TVerifiableByVerifyMacro for Option<T> {
    fn is_verify_true(&self) -> bool {
        self.is_some()
    }
}
impl<TOk, TErr> TVerifiableByVerifyMacro for Result<TOk, TErr> {
    fn is_verify_true(&self) -> bool {
        self.is_ok()
    }
}
macro_rules! verify {($e: expr) => {{
    let e = $e;
    assert!(e.is_verify_true(), "verify!({}): {:?}", stringify!($e), e)
    e
}}}

Using this macro, the aforementioned example could be written as:

assert!(!slice.empty()); // known to be nonempty by invariants
do_stuff_with_maximum(verify!(slice.iter().max()).unwrap());

If I can't unwrap the value, I get an error message mentioning slice.iter().max(), so that I can search my codebase quickly for the place where the error occurs. (Which is - in my experience - faster than looking through the backtrace for the origin of the error.)

phimuemue
  • 34,669
  • 9
  • 84
  • 115