Crate for lazily-evaluated option chains

I'm looking for a crate which does

Some((a, b, c)) = macro!(a.option(), b.option(), c.option())

where macro! expands to

match a.option() {
  Some(a) => match b.option() {
    Some(b) => match c.option() {
      Some(c) => Some((a, b, c)),
      None => None
    }
    None => None
  }
  None => None
}

I know there's the if_chain crate but I don't want to use it since it puts the whole if block's code inside of a macro, which is bad for rustfmt.

I don't want to use a.map(|a| b.map(|b| ... either since closures have some other issues depending on where you're using them (e.g. sometimes can't await inside of them).

i.e. I want it to expand to the code above, as if it were written by hand.

The closest existing crate I could find is tuple-transpose but it is not a macro, so it evaluates its arguments eagerly (not lazily).

With the try_blocks feature on nightly, you can write this as:

Some((a, b, c)) = try { (a.option()?, b.option()?, c.option()?) }

Playground

Unfortunately, I don't seem to be good enough at macros to implement the requested macro without using try blocks or closures.

Eventually there will be try blocks. Their current (unstable) form looks like this:

let abc: Option<_> = try { (a.option()?, b.option()?, c.option()?) };

dbg!(&abc); // Some(42, 42, 42)

(playground)

The restrictions of not using closures and not having too much code in a macro are quite strict, I’ll see if I can come up with anything. Edit: ah wait, you only want the block of an if let-kind of block not in the macro anymore, well that sounds like a more realistic requirement.

Right. Put into code, it'd be

if let Some((a, b, c)) = macro!(a.option(), b.option(), c.option()) {
    // This is what I don't want inside of the macro
}

I can take a stab at implementing it... Just thought I'd ask if there wasn't already a crate like that.

Try blocks look great! Though I'm using stable for the project I'm working on and this looks doable on stable.

No need, I just implemented it xD

macro_rules! options {
    ($e:expr $(, $es:expr)*$(,)? $(;# $($i:ident)*)?) => {
        if let Some(x) = $e {
            options!($($es),* ;# $($($i)*)? x)
        } else { None }
    };
    ($(;# $($i:ident)*)?) => {
        Some(($($($i),*)?))
    };
}

(hygiene will make sure that all the xes are different variables)

2 Likes

Thanks @steffanh. And I hope results! is right as well.

macro_rules! results {
    ($e:expr $(, $es:expr)*$(,)? $(;# $($i:ident)*)?) => {
		match $e {
			Ok(x) => results!($($es),* ;# $($($i)*)? x),
			Err(e) => Err(e)
		}
    };
    ($(;# $($i:ident)*)?) => {
        Ok(($($($i),*)?))
    };
}

It's intended to error on only the first one found.