HiveBrain v1.2.0
Get Started
← Back to all entries
debugrustMajor

Option types and early returns. return an Error when is_none()

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
errorreturnreturnsandis_noneoptionwhenearlytypes

Problem

Using match (like in bar) seems to be a common approach..

#[derive(Debug)]
pub enum MyErrors {
    SomeError,
}

fn foo(x: Option) -> Result {
    if x.is_none() {
      return Err(MyErrors::SomeError);
    } 

    // .. some long code where more options
    // are checked and matched 
    // The ok here is just so the code is simple and compiles
    Ok(x.unwrap() * 2)
}

fn bar(x: Option) -> Result {
    match x {
        None => {
            return Err(MyErrors::SomeError)?;
        }
        Some(v) => {
           // .. some long code where more options
           // are checked and matched 
           // The ok here is just so the code is simple and compiles
           Ok(x.unwrap() * 2)
        }
    }
}

fn main() {
    foo(Some(1));
    bar(Some(2));
}


However, early returns (such as in foo) significantly reduce how nested the code looks like. If there are multiple times when an option has to be unwrapped or an error returned, code like bar gets very nested...

What is the recommended practice for early returning an error in the case of empty options?

Solution

If a longer method chain is undesirable due to complex logic inside, there are still a few readable, low-indent options.

ok_or and ?

We can convert an Option to a Result with a desired error, and immediately unwrap it with the ? operator. This solution probably provides the least indent possible, and can be easily used to "unwrap" multiple Options.

fn bar1(x: Option) -> Result {
    let x = x.ok_or(MyErrors::SomeError)?;
    // A lot of stuff going on.
    Ok(x * 2)
}


This will evaluate the error inside ok_or regardless of whether or not it will actually be used. If this computation is expensive, ok_or_else, which produces the error lazily, will be more efficient (related question).

if let

This solution can still lead to a staircase of code if nested, but may be more appropriate if the else branch logic is more involved.

fn bar2(x: Option) -> Result {
    if let Some(x) = x {
        // Lot of stuff here as well.
        Ok(x * 2)
    } else {
        Err(MyErrors::SomeError)
    }
}

Code Snippets

fn bar1(x: Option<u64>) -> Result<u64, MyErrors> {
    let x = x.ok_or(MyErrors::SomeError)?;
    // A lot of stuff going on.
    Ok(x * 2)
}
fn bar2(x: Option<u64>) -> Result<u64, MyErrors> {
    if let Some(x) = x {
        // Lot of stuff here as well.
        Ok(x * 2)
    } else {
        Err(MyErrors::SomeError)
    }
}

Context

Stack Overflow Q#55307746, score: 73

Revisions (0)

No revisions yet.