marcantonio
marcantonio

Reputation: 1069

Is there a less verbose way to extract values from Options in Rust

I find myself doing something like the following a lot:

fn foo() -> Result<i32, String> {
    let cur = match something_that_returns_an_option() {
        Some(cur) => cur,
        None => return Err("Some error"),
    };
    
    // use `cur`
    
    1
}

If I need several variables, I'm left with this pattern over and over again, or nested if lets/matches.

I there a more ergonomic way to handle repeatedly extracting values from Options?

Upvotes: 11

Views: 6640

Answers (2)

at54321
at54321

Reputation: 11766

In your example you want to not just continue, break or return a regular value, but return an error. For that particular case, the Aiden4's answer is the way to go. Or, if you are using anyhow, you should see this.

But I've been in situations where I want to unwrap or (in the case of None) directly continue, break or return a non-error value.

Update (Rust 1.65+)

let-else

The let-else feature was stabilized in Rust 1.65. Here is an example of how it can be used:

let options = vec![Some(74), None, Some(9)];
for o in options {
    let Some(v) = o else { continue };
    println!("v = {v}");
}

Older answer (and alternative solutions)

Rust (still) doesn't provide a short and concise way to do exactly that.

Here is a "one-liner" which kinda does the trick, but is still a bit verbose:

let v = if let Some(d) = some_option_value { d } else { continue; };

If you want a shorter solution, here is something to consider...

A macro

You can write a macro like this:

macro_rules! unwrap_or {
    ($e:expr, $or_do_what:expr) => {
        if let Some(d) = $e { d } else { $or_do_what }
    };
}

That will allow you to write code like this:

let options = vec![Some(74), None, Some(9)];
for o in options {
    let v = unwrap_or!(o, continue);
    // ...
}

That's a trivial example, but I think the biggest benefit can come if you need to perform multiple checks, so that instead of writing something "idiomatic" like this:

for thing in things {
    if let Some(a) = thing {
        // ...
        if let Some(b) = a.another_opt {
            // ...
            if let Some(c) = a.yet_another_opt {
                // ...
            }
        }
    }
}

, you can simplify the code by avoiding the nesting of multiple blocks like this:

for thing in things {
    let a = unwrap_or!(thing, continue);
    // ...
    let b = unwrap_or!(a.another_opt, continue);
    // ...
    let c = unwrap_or!(a.yet_another_opt, continue);
    // ...
}

Whether that's a good practice is subjective, of course.

Upvotes: 8

Aiden4
Aiden4

Reputation: 2664

You are looking for Option::ok_or. It lets you map an Option into a Result with the provided error. Combined with the ? operator you clean things up nicely:

fn foo() -> Result<i32, String> {
    let cur = something_that_returns_an_option().ok_or("some error")?;
        
    Ok(cur + 1)
}

Playground

Option::ok_or_else might also be helpful, as it evaluates the error branch lazily.

Upvotes: 13

Related Questions