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
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
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.
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>.
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.
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 might end up being two different routes to get from A to C, via intermediate types B or D. Which one should be picked, given that they might not be identical?
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.
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(())
}