13

I have some code that looks like this:

f(a).and_then(|b| {
    g(b).and_then(|c| {
        h(c).map(|d| {
            do_something_with(a, b, c, d)
        })
    })
})

Where f, g, and h return Option values. I need to use all the intermediate values (a, b, c, and d) in the do_something_with calculation. The indentation is very deep. Is there a better way to do this? Ideally it would look something like this (which of course doesn't work):

try {
    let b = f(a);
    let c = g(b);
    let d = h(c);
    do_something_with(a, b, c, d)
} rescue NonexistentValueException {
    None
}
Adrian
  • 14,931
  • 9
  • 45
  • 70
  • do all the functions return the same option type? or do they change the type? – oli_obk Jul 02 '15 at 08:23
  • 1
    in case they are: here's a macro-free solution: http://is.gd/eItCTh any other solutions require value generics or variadic generics or some trait like [FixedSizeArray](https://doc.rust-lang.org/nightly/core/array/trait.FixedSizeArray.html) just for tuples – oli_obk Jul 02 '15 at 08:33

2 Answers2

13

Rust 1.22

The question mark operator now supports Option, so you can write your function as

fn do_something(a: i32) -> Option<i32> {
    let b = f(a)?;
    let c = g(b)?;
    let d = h(c)?;
    do_something_with(a, b, c, d) // wrap in Some(...) if this doesn't return an Option
}

Rust 1.0

The Rust standard library defines a try! macro (and, equivalently, the ? operator, as of Rust 1.13) that solves this problem for Result. The macro looks like this:

macro_rules! try {
    ($expr:expr) => (match $expr {
        $crate::result::Result::Ok(val) => val,
        $crate::result::Result::Err(err) => {
            return $crate::result::Result::Err($crate::convert::From::from(err))
        }
    })
}

If the argument is Err, it returns from the function with that Err value. Otherwise, it evaluates to the value wrapped in Ok. The macro can only be used in a function that returns Result, because it returns the error it meets.

We can make a similar macro for Option:

macro_rules! try_opt {
    ($expr:expr) => (match $expr {
        ::std::option::Option::Some(val) => val,
        ::std::option::Option::None => return None
    })
}

You can then use this macro like this:

fn do_something(a: i32) -> Option<i32> {
    let b = try_opt!(f(a));
    let c = try_opt!(g(b));
    let d = try_opt!(h(c));
    do_something_with(a, b, c, d) // wrap in Some(...) if this doesn't return an Option
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
6

Inspired from the concept of try! for Result, let's wrap our own macro to early-return from the scope if the monad drops to None.

macro_rules! get(
    ($e:expr) => (match $e { Some(e) => e, None => return None })
);

(Stolen from this reddit thread)

Now you can run your code linearly:

fn blah() -> Option<...> { // ... is the return type of do_something_with()
    let a = 123;
    let b = get!(f(a));
    let c = get!(g(b));
    let d = get!(h(c));
    do_something_with(a, b, c, d)
}

(runnable gist)

mdup
  • 7,889
  • 3
  • 32
  • 34