Option<Result<T, E>> and ? returning a 'Some<Err<E>>'

I have a function with return type Option<Result<T, E>> where

  1. each function is a 'rule'

  2. None = this rule is not relevant, try next rule

  3. Some(Ok(..)) = applied rule, got result

  4. Some(Err(..)) = applied rule, got error

fn foo(...) -> Option<Result<T, E>> {

  let x = ... ?; // returns None if it doesn't match.

  let y: Result<T2, E> = blah(x);

  let z: T2 = y?; // if y is an E, let's return Some(Err(E))
  // is this line possible? The best I can think of is:

  match y {
    Err(e) => return Some(Err(e)),
    (Ok(z) => ...

  // but I don't like this 'solution', as I actually have:

    let y1: Result<T2, E> = blah(x);
    let y2: Result<T3, E> = foobar(x);
  /// ... and more

Am I basically forced to create two functions here?

One option could be to write

let z: T2 = match y.transpose() {
    Err(e) => return Some(Err(e)),
    Ok(v) => v,

But this would be made much easier if all of your functions returned Result<Option<T>, E> rather than Option<Result<T, E>>. Is there a reason you chose the latter over the former?

Edit: ahh, I just realized that Option being the outer type makes let x = ... ? actually work.

I don't know of a way to have niceness for both returning None on None and returning Some(Err) on Some(Err). Would it be at all reasonable to just return a Result<T, E> and have your error type contain a variant for "rule not relevant", maybe with some combinator functions for ignoring it?

1 Like

Maybe return Result<T, Option<E>>, Err(None) means the rule is irrelevant, and Err(Some(_)) means tried and failed.


I have decided to split this into two functions/layers. After all, these is two layers of logic:

  1. do the dynamic types match for this rule to apply ?

  2. when we apply this rule, do we get answer or an error ?