quite new to rust and trying to find myself around traits - I am working on a custom testing module and am trying to construct Result types where the Err is of a TestIssue enum that either implements an Error or Failure. I essentially want any Error type that implements std::error::Error to be convertible to a TestIssue::Error type, but also want to be able to handle cases like anyhow::Error where I can call into() to convert to an std::error::Error

#[derive(Debug)]
pub enum TestIssue<E: std::error::Error> {
    Error(E),
    Failure(String),
}

impl<E: std::error::Error + std::convert::From<anyhow::Error>> From<anyhow::Error> for TestIssue<E> {
    fn from(error: anyhow::Error) -> TestIssue<E> {
        TestIssue::Error(error.into())
    }
}

impl<E: std::error::Error> From<E> for TestIssue<E> {
    fn from(error: E) -> TestIssue<E> {
        TestIssue::Error(error)
    }
}

#[async_trait]
pub trait RunnableTest {
    type Error: std::error::Error;
    async fn run(&self) -> Result<(), TestIssue<Self::Error>>;
}

Strangely in some compiler errors I get, it says that the From implementations are conflicting even though anyhow::Error doesn't implement the std::error::Error trait from the docs.

conflicting implementations of trait `std::convert::From<anyhow::Error>` for type `TestIssue<anyhow::Error>`

This is confusing to me as I'd expect the From implementations to be unrelated to each other if the fact that anyhow::Error doesn't implement the std::error::Error trait still holds true...

impl<E: std::error::Error + std::convert::From<anyhow::Error>> From<anyhow::Error> for TestIssue<E> {
   | --------------------------------------------------------------------------------------------------- first implementation here
...
18 | impl<E: std::error::Error> From<E> for TestIssue<E> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `TestIssue<anyhow::Error>`

Another issue I'm facing has to do with the RunnableTest::Error type - it appears if I have it impl std::error::Error it causes issues when attempting to return anyhow::Error in concrete implementations - I believe this has to do with the earlier issue of anyhow::Error only implementing into() but not the trait directly - When I try to change it to something like this:

type Error: Into<dyn std::error::Error + Sized>;

I get other issues like this:

31 |     type Error: Into<dyn std::error::Error + Sized>;
   |                          -----------------   ^^^^^ additional non-auto trait

Curious about how reasonable the current design is or if I'm missing something with respect to traits that could make this more simple. Given how new I am to the language, any other guidance or general comments would be much appreciated as well!

1

There are 1 best solutions below

0
cadolphs On

If you read the full error message, you'll note that it says:

note: upstream crates may add a new impl of trait std::error::Error for type anyhow::Error in future versions

This is a bit similar to the orphan rules. Right now the anyhow error doesn't implement the std::error::Error trait but it may choose to do so in the future and you have no control over that. This would then introduce subtle behavioral changes in your code.

Therefore, using generics and traits and trait implementations is a bit more restrictive right now than it might have to be. See discussion here:

How do I work around the "upstream crates may add a new impl of trait" error?

The way to work around this is to provide your trait implementations for concrete types, possibly using macros to reduce the amount of boilerplate, or to pick one interface (either the errors from std or the anyhow crate but not both) and stick with that.

Maybe work that out first and then come back with another question about the RunnableTest stuff? :)