Result type auto error type coersion

The problem:

If you use some libraries every library brings its own error type. These error types then lie outside of your crate therefore you can't coerce them with the ? operator in one function scope. You therefore then create your own enum type or struct that then implements From for all the error types (and of course std::error::Error) The problem is when you simply want to return a result:
fn foo() -> Result<i32, MyError> {
    lib1::foo()?;
    lib2::foo() //Error different error types
} 

Solutions

One solution is to call lib2::foo().map_error(MyError::From). But this is very wordy and could be very well done by the compiler. We just need a way to tell the compiler that he needs to use the From implementation. My current solution is an extention function for Result:
fn auto_err<F>(self) -> Result<T, F>
where F: From<E>;

This helps a lot to reduce the boilerplate of obvious from into conversions. It might even be worth a keyword but that's debatable. When it's chosen to get a keyword I would suggest the postfix ! The sample code above with the operator would look like this:

fn foo() -> Result<i32, MyError> {
    lib1::foo()?;
    lib2::foo()! 
} 

It might be beneficial to from/into all generic parameters of the type operated on (in this case Result).

Is there any problem with this?

fn foo() -> Result<i32, MyError> {
    lib1::foo()?;
    Ok(lib2::foo()?)
} 
6 Likes

I think the best way forward for this is the "you put ? on everything fallible" pattern (perhaps plus try blocks).

Because the best answer is not "make the last one even more special with another operator", but "make the last one the same as the others":

fn foo() -> Result<i32, MyError> {
    try {
        lib1::foo()?;    // it's fallible so it's `?`'d
        lib2::foo()?;    // it's fallible so it's `?`'d
        lib3::foo()?     // it's fallible so it's `?`'d
    }
} 

On stable today you can write that as

fn foo() -> Result<i32, MyError> {
    Ok({
        lib1::foo()?;    // it's fallible so it's `?`'d
        lib2::foo()?;    // it's fallible so it's `?`'d
        lib3::foo()?     // it's fallible so it's `?`'d
    })
} 
3 Likes

I think after some afterthought it simply lacks a From implementation for result. Something like this:

impl<T, E, U, F> From<Result<T, E>> for Result<U, F> 
where U: From<T>,  F: From<E> {
     
     fn from(source: Result<T, E>) -> Self {
          source.map(U::from).map_err(F::from)
     } 
} 

This would allow for:

fn foo() -> Result<i32, MyError> {
    lib1::foo()?;
    lib2::foo().into()
}  

Which is in my opinion pretty reasonable.

1 Like

I agree, but it can't be written today because it overlaps with the identity impl.

Would be nice, though!

That could be nice, but it conflicts with the core impl<T> From<T> for T;

The possibility of a helper method was tried in rfc 1996 but finally discarded because of lack of motivation in the success conversion.

In that RFC thread @withoutboats comments about the possibility of implementing the trait:

Someday it will work (because we want intersection impl specialization as soon as its feasible to implement it), and the impl should exist so that you can use it generically.

That was in 2017, I have no idea what that "someday" would be.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.