Error conversion into struct with intermetiate type

See code below, the commented out line would do the trick, but can this be achieved more implicitly:

  • I have a ThirdParty which can be converted into MyError
  • MyError can be converted into Bar
  • How can I use this relationship to convert ThirdParty into Bar?

I understand the error message but wonder why From<MyError> is not created as well in some sense.

Thanks for any input,
Philipp

use thiserror;

#[derive(thiserror::Error, Debug)]
enum MyError {
    #[error("Some error")]
    Foo(#[from] ThirdParty),
}

// another error, coming from a third party
#[derive(thiserror::Error, Debug)]
enum ThirdParty {
    #[error("Some error")]
    Foo,
}

fn may_yield_foo() -> Result<(), ThirdParty> {
    Ok(())
}

#[derive(Debug)]
struct Bar;

impl From<MyError> for Bar {
    fn from(e: MyError) -> Self {
        Bar { }
    }
}

fn main() -> Result<(), Bar> {
    // let bar = may_yield_foo().map_err(MyError::Foo)?;
    
    let bar = may_yield_foo()?;
    
    Ok(())
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: `?` couldn't convert the error to `Bar`
  --> src/main.rs:32:30
   |
29 | fn main() -> Result<(), Bar> {
   |              --------------- expected `Bar` because of this
...
32 |     let bar = may_yield_foo()?;
   |                              ^ the trait `From<ThirdParty>` is not implemented for `Bar`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = help: the trait `From<MyError>` is implemented for `Bar`
   = note: required for `Result<(), Bar>` to implement `FromResidual<Result<Infallible, ThirdParty>>`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (bin "playground") due to previous error

Did you read the error? It clearly says

So how do you expect the following to work without Bar: From<ThirdParty>? Rust Playground

struct ThirdParty;
struct MyError;
struct Bar;

impl From<ThirdParty> for MyError {
    fn from(_: ThirdParty) -> MyError { MyError }
}
impl From<MyError> for Bar {
    fn from(_: MyError) -> Bar { Bar }
}

fn f<T: Into<Bar>>(t: T) -> Bar {
    t.into()
}

fn main() {
    f(MyError); // ok
    f(Bar); // ok
    f(ThirdParty); // ???
}

@vague Yes, I read the error and clearly see what the compiler is missing:

the trait From<ThirdParty> is not implemented for Bar

What I wonder is: If I have From<A> for B and From<B> for C, can I use this to get From<A> for C, when I am not mistaken this is the situation I have.

Or do I overlook somethig obvious?

I can understand that the inference of the compiler goes only one level deep, is this the case?

Yes. There is only one impl w.r.t Into trait:

impl<T, U> Into<U> for T
where
    U: From<T>,

but you want

impl<T0, U, T1> Into2<U> for T0
where
    U: From<T1>,
    T1: From<T0>,
{
    fn into2(self) -> U { U::from(T1::from(self)) }
}

which doesn't exist and is not valid in Rust at all Rust Playground

error[E0207]: the type parameter `T1` is not constrained by the impl trait, self type, or predicates
  --> src/main.rs:31:13
   |
31 | impl<T0, U, T1> Into2<U> for T0
   |             ^^ unconstrained type parameter
2 Likes

What if some other type, D, implemented From<A> for D and From<D> for C down the track? Then the compiler would have two possible routes from A to C (A->B->C and A->D->C) and the situation would be ambiguous.

You also have the fact that every type implements From<T> for T, so actually we've already got this situation.

For your use case, the best strategy would probably be to manually write the extra From implementation. That way there's no ambiguity.

4 Likes

No, not at all – rather, there's no such "inference" going on whatsoever.

The compiler doesn't understand the meaning of From. It doesn't "know" that it's a "conversion". It's not reasoning like a human, and you shouldn't expect it to. It is merely a formal system, and it treats From::from() exactly the same as other traits and their methods. It can't therefore go, "well, if A is convertible to B and B is convertible to C, then A must be convertible to C as well". It simply doesn't "think" in terms of humanely-useful concepts such as "convertible".

It merely looks at what traits are implemented, desugars the ? operator with a single call to From::from(), and reports an error if that implicit call is a type error because the target type doesn't implement From<Source>.

1 Like

Thank you @vague @Michael-F-Bryan and @H2CO3 for your valuable insights.

In my case, I have already a solution which roughly follows the commented line in my initial post, and your answers made me content with it. Before I asked the question and went through your answers, I kept asking myself whether I don't use an idiomatic short cut.

"Shortcuts" are hardly ever idiomatic.

Especially if they replace simple and composable local reasoning with magic action at a distance. Rust abhors global analysis (because so does the human mind).

There are multiple reasons why it wouldn't be a good idea to do this automatically, even if it is feasible to implement:

Error wrappers are an especially simple case, but they still shouldn't do this, because the error wrapper should be telling the user about the context of the error: "how does this lower-level error fit into the higher-level picture?" And so picking a wrapper just because it exists would give potentially incorrect information.

2 Likes

To explain this to myself I figured out that in my example, I need an intermediate function which triggers the mechanisms explained by @H2CO3 above.

Playground

Context in my case: I use axum and have an Error which can be converted into a Response, and in my route handler I use reqwest which itself might return an error. Although I have From<reqwest::Error> for Error and From<Error> for Response, I cannot use the ? operator conveniently in my routes unless I either manually map the error or use a pattern like the one in the playground.

Nice side effect: Encourages to keep the handlers small and put the interactions with other APIs into separate functions.

I don't want to say that this problem is of interest for anyone else, or that my playground reads particularly well.

#![allow(unused)]
#![allow(dead_code)]

use thiserror;

#[derive(thiserror::Error, Debug)]
enum MyError {
    #[error("Some error")]
    Foo(#[from] ThirdParty),
}

// another error, coming from a third party
#[derive(thiserror::Error, Debug)]
enum ThirdParty {
    #[error("Third-Party error")]
    ThirdPartyFoo,
}

fn may_yield_third_party_error() -> Result<u16, ThirdParty> {
    Ok(42)
}

#[derive(Debug)]
struct Bar;

impl From<MyError> for Bar {
    fn from(e: MyError) -> Self {
        Bar { }
    }
}

fn may_yield_error() -> Result<u16, MyError> {
    Ok(may_yield_third_party_error()?)
}

fn main() -> Result<(), Bar> {
    let value = may_yield_error()?;
    dbg!(value);
    
    // -- or --
    let value = may_yield_third_party_error().map_err(MyError::Foo)?;
    
    // Difference for the author: 
    
    // In the latter case, the third party error is
    // known to correspond to MyError::Foo (by looking at the code)
    
    // In the first case, the author only knows that From<ThirdPartyError> 
    // is implemented for MyError, but does not know which error variant is
    // the correct one.
    
    dbg!(value);
    Ok(())
}
1 Like