Convenience method for flipping Option<Result> to Result<Option>?

When working with optional value via .and_then() and .map() I often end up with Option<Result<T>>, but I want to check the result and end up with Option<T> or return from a function early.

Does Option have a nice method for this? So far the shortest I know is:

let optional = if let Some(tmp) = optional {
     Some(tmp?)
} else {
    None
};

but I'd prefer something shorter like optional.flip_inside_out()?.

2 Likes

I want this all the time. My trait method is usually called invert, but I don't really like that name.

The current shortest way I know is (hat tip @jseyfried) :

fn invert<T, E>(x: Option<Result<T, E>>) -> Result<Option<T>, E> {
    x.map_or(Ok(None), |v| v.map(Some))
}
3 Likes

For curiosity, the Try-generic version for Options.

#![feature(try_trait)]
use std::ops::Try;

fn flob2<T, R>(r: Option<T>) -> R
    where T: Try,
          R: Try<Ok=Option<T::Ok>, Error=T::Error>
{
    R::from_ok(if let Some(r) = r {
        Some(r?)
    } else {
        None
    })
}

We can go deeper!

#![feature(try_trait)]
use std::ops::Try;

fn flob2<T, R>(r: Option<T>) -> R
where
    T: Try,
    R: Try<Ok = Option<T::Ok>, Error = T::Error>,
{
    flob4(r)
}

fn flob4<T, U, R, S>(x: T) -> R
where
    R: Try<Ok = S, Error = U::Error>,
    U: Try,
    S: Try<Ok = U::Ok, Error = T::Error>,
    T: Try<Ok = U>,
{
    R::from_ok(match x.into_result() {
        Ok(ok) => S::from_ok(ok?),
        Err(err) => S::from_error(err),
    })
}
2 Likes

Or with fewer parameters (and less fun):

fn flobbed<T, R>(x: T) -> R
where
    T: Try,
    T::Ok: Try,
    R: Try<Error = <T::Ok as Try>::Error>,
    R::Ok: Try<Ok = <T::Ok as Try>::Ok, Error = T::Error>,
{
    R::from_ok(match x.into_result() {
        Ok(ok) => R::Ok::from_ok(ok?),
        Err(err) => R::Ok::from_error(err),
    })
}

I've wanted the same on multiple occasions now. The result crate provides exactly such methods (also calling them invert), but it's still at 0.0.1 and hasn't been updated in a year …

So about bikeshedding of the name. The Result/Option has a naming scheme for all the ok/and/else methods. How about:

  • Simply .ok() on Option? There's Result.ok(), and this would pretend it's almost like calling .ok() on the inner result.

  • .ok_or_some()? Option already has ok_or_* methods for converting to Result. The some suffix is there to follow the naming pattern and to clarify that errors are not changed to None.

  • and_ok()? and_* methods process Option/Result without unwrapping the object, and ok is hinting Result.ok() happening inside.

1 Like

Found this thread, because I need something like this right now. To contribute to the bike shedding:
How about calling it evert or upend. I'm not a native speaker though. So take it with a grain of salt.

x.map_or(Ok(None), |v| v.map(Some))

Until something like this is packaged generally, if you ever think "what was that magic invocation, again?", I've added it to RBE. I was tired of trying to remember where I last used it or to reconstruct it myself.

https://rustbyexample.com/error/multiple_error_types/option_result.html

4 Likes

Relatedly, I've stumbled upon the inverse case recently -- wherein a private method of an iterator I've been writing returned Result<Option<T>, E> so that I could use ? inside this method. Buf when writing next(), I have to invert this to match the iterator protocol.

Is there a similarly short "incantation" for this use-case? The best I could find is r.map(|o| o.map(Ok)).unwrap_or_else(|e| Some(e)), which is rather sub-par in my opinion.

But then again, maybe using Result<Option<T>, E> internally is the wrong approach to begin with ...

That's an interesting case. Iterators over results are OK (there's collect::<Result<Vec<_>,_>() that handles the errors), so conversion of Result<Option> to Option<Result> seems like a good use-case too.

1 Like

TIL there's word evert which means to turn inside out, which seems like an accurate description of what happens here. I'm not sure if it's not too obscure.

I'm not sure about this term... it has bad gamer memories attached to it :stuck_out_tongue:

1 Like

Despite all, it is not functionality that one needs terribly often. And assuming these were inherent functions implemented on Option and Result, they would be roundabout as discoverable as the collect::<Result<_, _>>(), which is another obscure, yet strangely useful feature. That's why a name like "evert" wouldn't bother me a lot, tbh. It's certainly more descriptive than "flip", "flop", "flob" or similar.

2 Likes

How does this look?

pub trait InsideOut {
    type Result;
    fn inside_out(self) -> Self::Result;
}

impl<T, E> InsideOut for Option<Result<T, E>> {
    type Result = Result<Option<T>, E>;
    fn inside_out(self) -> Self::Result {
        if let Some(a) = self {
            match a {
                Ok(o) => Ok(Some(o)),
                Err(e) => Err(e),
            }
        } else {
            Ok(None)
        }
    }
}

impl<T, E> InsideOut for Result<Option<T>, E> {
    type Result = Option<Result<T, E>>;
    fn inside_out(self) -> Self::Result {
        match self {
            Ok(o) => o.map(|o| Ok(o)),
            Err(e) => Some(Err(e)),
        }
    }
}

:+1: on the name choice!

To avoid confusion with the actual Result struct, though, it might be better to name the associated type Output instead of Result.

The implementation is simple enough. Since this is hiding the inconvenient conversion behind a helper method anyway, there is no need to be overly "clever" inside the method. So, a three-way match might be a bit easier to read:

match option_of_result {
    Some(Ok(x)) => Ok(Some(x)),
    Some(Err(e)) => Err(e),
    None => Ok(None),
}

@troiganto Thanks. I think you're right about both points.

So what should I do with it (after I implement your suggestions?) Publish as a new crate (inside_out? - I think I'm going to go with this one)? Submit a PR to an existing one?

I'm still new to the scene, so others might have better suggestions. A new crate might make sense here. You could also publish it instantly with version 1.0.0, considering that breaking changes are rather unlikely.

There's also the result crate, which I've mentioned before and which is kind of hanging in limbo. It essentially does the same thing, so you could contact the owner and ask them if they could add you as an owner.

I've pinged the author of result

List of things to consider:

  • trait and function naming: InsideOut and inside_out()? I think the name is very obvious and natural, with no other meanings. invert has other meanings in math, so it's potentialy confusing.
  • crate name; if we're going with InsideOut than insideout /inside_out crate makes sense
  • we could potentially implement it for other things. Eg. iterators of results and options.

Please let me know what you think. I'd like to be done with it by the end of the day. If someone is willing to do it, I'm even happier. If not I'll do it.

Well, I'll at least bump result up to 1.0.0 as I wasn't aware that there were concerns about it being in limbo, there was just nothing else it needed. I do like the idea of additional iterator adapters and extension traits though.