How to map result of first function with second function, both of which may fail, but with different Err types?

I have a function that returns a Result<T,E>. If and only if this first function succeeded, I need to apply a second function on the result value. Otherwise, I just want to pass on the error. The second function, too, may fail and therefore has return value of type Result<T,E>.

What is the "cleanest" way to do this in Rust?

A simple func1().map(|val| func2(val)) won't do, as it produces an Result<Result<T,E>,E>, but I need to have just Result<T,E> as the final result!

func1().ok().map(|val| func2(val).ok()) has the same problem, that it produces strange Option<Option<T>> as the final result, instead of just Option<T>.

Also, func1().and_then(|val| func2(val)), which looks like exactly what I needed, doesn't do either, but instead results in the following error message:

mismatched types
expected enum `Result<_, ErrorTheType1>`
   found enum `Result<usize, ErrorTheType2>`

It seems the problem here is that func1() and func2() not have same E type :angry:

What actually works is:

enum MyErr {
    ErrType1(ErrorTheType1),
    ErrType2(ErrorTheType2)
}

func1().map_err(|err1| MyErr::ErrType1(err1))
    .and_then(|val| func2(val).map_err(|err2| MyErr::ErrType2(err2)));

But this seems very cumbersome and way too verbose for something that should be one-liner.

Can it be improved? Is there more "elegant" solution I'm missing?

Thanks.

Implement From<E1> for CommonError, repeat with E2 (perhaps via thiserror), then use the ? operator.


If the context where you are doing this is sufficiently typed to guide inference, you can also write an extension trait:

trait ResultExt<T, E>: Sized {
    fn and_then<F, U, E2>(self, f: F) -> Result<U, E>
    where
        F: FnOnce(T) -> Result<U, E2>,
        E: From<E2>;
}

impl<T, E1, E> ResultExt<T, E> for Result<T, E1>
where
    E: From<E1>,
{
    fn and_then<F, U, E2>(self, f: F) -> Result<U, E>
    where
        F: FnOnce(T) -> Result<U, E2>,
        E: From<E2>,
    {
        Ok(f(self?)?)
    }
}

What is CommonError? (can't find)

Or do you mean this is a new enum that I should define myself?

Yes.

I see.

Well, I can do something like this, but that's even more verbose than "my" crude solution!

enum CommonError {
    ErrType1(ErrorTheType1),
    ErrType2(ErrorTheType2)
}

impl From<ErrorTheType1> for CommonError {
    fn from(value: ErrorTheType1) -> Self {
        CommonError::ErrType1(value)
    }
}

impl From<ErrorTheType2> for CommonError {
    fn from(value: ErrorTheType2) -> Self {
        CommonError::ErrType2(value)
    }
}

fn test() -> Result<usize, CommonError> {
    Ok(func1()?.func2()?)
}

You can make this slightly nicer by realizing that MyErr::ErrType1/2 are already functions:

func1().map_err(MyErr::ErrType1)
    .and_then(|val| func2(val).map_err(MyErr::ErrType2));
1 Like

If this is a one-off situation, then don't bother with impl From…, but you can still make use of ? for the control flow, not the conversion.

fn test() -> Result<usize, CommonError> {
    Ok(func1()
        .map_err(CommonError::ErrType1)?
        .func2()
        .map_err(CommonError::ErrType2)?)
}
1 Like

As @paramagnetic hinted at above, I believe, these kind of implementations can be easily generated if you use the thiserror crate for instance.

Look for the #[from] attribute in their documentation.

Having an Error implementation might be desirable for such an enum, anyway. Otherwise, other deriving helper crates also offer derives for From not coupled to Error.

1 Like

I just found that flatten() is extremely useful here! :grinning:

Probably shortest solution:

test() -> Option<usize> {
    func1().ok().map(|val| func2(val).ok()).flatten()
}

That's why I suggested thiserror. At some point, you'll likely end up depending on it anyway.

Okay, thanks, I will probably look into it.

For now, the solution based on flatten() is nice and short (and sufficient for my use-case).

Doesn't it lose the error types completely, though?

Yes, it does. But in my use-case it is okay to just get an Option<T> as result, because the caller only needs to know whether everything worked (and we have a value) or not :smiling_face:

Then you don't need flatten(), it can be just func1().ok().and_then(|val| func2(val).ok()).

3 Likes

You can also write this with ?:

fn test() -> Option<usize> {
    let val = func1().ok()?;
    func2(val).ok()
}

or

fn test() -> Option<usize> {
    func2(func1().ok()?).ok()
}
1 Like

.map(f).flatten() is always equivalent to .and_then(f) (or .flat_map(f) in the case of iterators). [1]


  1. This is the so-called monadic bind operation. ↩︎

1 Like