How can I use generics to return different Errors from a function using `anyhow`?

Hi all,

I'm working on a larger code base and I've made a toy example that I think replicates the error. This comes from trying to integrate actix and anyhow errors. I have a fn that returns different errors, some are anyhow::Error and others are actix_web::error::ResponseError. I'm trying to use the generic E to be generic for anyhow or other errors and using a where clause on E that can be converted From.

I've replaced the actix error with CustomError. Given the following code (sorry for the length):

[dependencies]
anyhow = "1.0"
derive_more = "0.99.2"
rand = "0.7"


#[macro_use]
use anyhow::Error;
use derive_more::Display; // naming it clearly for illustration purposes
use rand::{
    distributions::{Distribution, Standard},
    thread_rng, Rng,
};

impl Distribution<CustomError> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> CustomError {
        match rng.gen_range(0, 4) {
            0 => CustomError::CustomOne,
            1 => CustomError::CustomTwo,
            2 => CustomError::CustomThree,
            _ => CustomError::CustomFour,
        }
    }
}

/// randomly returns either () or one of the 4 CustomError variants
fn do_something_random() -> Result<(), CustomError> {
    let mut rng = thread_rng();

    // 20% chance that () will be returned by this function
    if rng.gen_bool(2.0 / 10.0) {
        Ok(())
    } else {
        Err(rand::random::<CustomError>())
    }
}

#[derive(Debug, Display)]
pub enum CustomError {
    #[display(fmt = "Custom Error 1")]
    CustomOne,
    #[display(fmt = "Custom Error 2")]
    CustomTwo,
    #[display(fmt = "Custom Error 3")]
    CustomThree,
    #[display(fmt = "Custom Error 4")]
    CustomFour,
}

impl std::error::Error for CustomError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.source()
    }
}

#[derive(Debug)]
struct MyAnyhowError(Error);

impl std::fmt::Display for MyAnyhowError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<MyAnyhowError> for CustomError {
    fn from(item: MyAnyhowError) -> Self {
        rand::random::<CustomError>()
    }
}

impl From<CustomError> for MyAnyhowError {
    fn from(item: CustomError) -> Self {
        MyAnyhowError(Error::new(item))
    }
}

impl From<Error> for MyAnyhowError {
    fn from(item: Error) -> Self {
        MyAnyhowError(item)
    }
}

impl From<MyAnyhowError> for Error {
    fn from(item: MyAnyhowError) -> Self {
        item.0
    }
}

fn do_something_anyhow() -> Result<(), Error> {
    Err(Error::msg("anyhow"))
}

fn do_something<E>() -> Result<(), E>
where
    E: From<MyAnyhowError>,
{
    do_something_random()?; // <- First compilation error

    do_something_anyhow()?; // <- Second compilation error
    Ok(())
}

fn main() {
    do_something::<anyhow::Error>().unwrap();
}

This is the exception message

`?` couldn't convert the error to `E`

the trait `std::convert::From<CustomError>` is not implemented for `E`

note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
note: required by `std::convert::From::from`

The exception message is quite confusing since I've implemented From for both of the Error types (in both directions). How can I solve this?

TIA

From is not transitive - it means impl From<A> for B and impl From<B> for C doesn't implies C: From<A>. You have impl From<CustomError> for MyAnyhowError and the generic bound is E: From<MyAnyhowError>. But it doesn't implies E: From<CustomError> so you can't convert CustomError into E using the ? operator.

Since you are converting two different error types into E, you can only accept types that implement both conversions. This compiles:

fn do_something<E>() -> Result<(), E>
where
    E: From<CustomError> + From<anyhow::Error>,
{
    do_something_random()?;
    do_something_anyhow()?;
    Ok(())
}

I'm amazed how quickly there were replies. Thanks!

If I wanted to use a single type for E: From<MyAnyhowError> would I have to use an enum like

enum MyAnyhowError {
    CustomError,
    anyhow::Error
}

If you want to convert all errors into MyAnyhowError then you don't need any generics. Just make your function return MyAnyhowError, and then the ? operator will do the conversion automatically:

fn do_something() -> Result<(), MyAnyhowError> {
    do_something_random()?;
    do_something_anyhow()?;
    Ok(())
}

fn main() {
    do_something().unwrap();
}

If you want to only convert from MyAnyhowError into a generic type E, then you can only call functions that return MyAnyhowError. (This is probably not what you want.)

By the way, if you're using anyhow, then you already have an error type that can wrap any other error (anyhow::Error), so you may not need to define any additional wrappers like MyAnyhowError. You can use anyhow::Result or anyhow::Error whenever you want to wrap several different errors in a common type.

2 Likes

That works! But I clearly don't understand generics or when to use them. I'm going to try and understand that a bit more. I'm refactoring code, so that generic was implemented by someone else and I don't really understand the case for doing it that way.

This is the code I'm trying to migrate from failure to anyhow. Can you try and explain why to use this or how to migrate it to anyhow?:

  pub async fn wrap<T, E>(
    self,
    ip_addr: String,
    fut: impl Future<Output = Result<T, E>>,
  ) -> Result<T, E>
  where
    E: From<failure::Error>,
  {
    let rate_limit: RateLimitConfig = actix_web::web::block(move || {
      // needs to be in a web::block because the RwLock in settings is from stdlib
      Ok(Settings::get().rate_limit) as Result<_, failure::Error>
    })
    .await
    .map_err(|e| match e {
      actix_web::error::BlockingError::Error(e) => e,
      _ => APIError::err("Operation canceled").into(),
    })?;
    ...

I'm familiar with anyhow::Error. The trouble started because actix_web used to implement From<failure> and the above conversion worked. When I tried to refactor it there isn't a conversion for anyhow.

the trait bound `anyhow::Error: actix_web::ResponseError` is not satisfied

the trait `actix_web::ResponseError` is not implemented for `anyhow::Error`

note: required because of the requirements on the impl of `std::convert::From<anyhow::Error>` for `actix_web::Error`

Because neither are local, I used a newtype struct as described here.

I suggest not mixing anyhow with enums. There are two philosophies of error handling in Rust:

  • One catch-all error type for everything, mostly where you don't care about the details of the error besides displaying it. This is used by applications. anyhow implements this kind.

  • Many specific enum-based error types with detailed cases and data for each possible cause of the error. This is used when you want to take specific actions based on error types (e.g. recover from the error). This is used by libraries. thiserror (from the same author as anyhow) helps implement this kind.

When you wrap anyhow in an enum, you get worst of both worlds: imprecise catch-all error type, and you still have to deal with enums and conversions.

So either adopt anyhow as your error type to use everywhere, or don't use it. If you have to deal with a library that uses it, that's a fault of library for using too-generic error type. You can contain the damage by converting anyhow errors to your custom enum variant, but a specific one, not just wrapping anyhow as MyError::Anyhow.

5 Likes

Thanks for the response. I think that's good advice but I don't feel that its answered the question I'm trying to understand. I don't think that I'm explaining the problem very well.

My goal is not to use an enum with anyhow. Rather, I'm trying to understand how to migrate a generic fn that can return either an anyhow::Error or an actix_web::Error. I believe the reason is tied to the way that actix middlewares are implemented but I'm still learning.

There's a lot of context but I'll take one last stab at getting to the error. I have created a newtype struct MyAnyhowError(anyhow::Error) since there is a requirement for the return error to implement ResponseError from actix_web. and I have a function wrap with the signature

pub async fn wrap<T, E>(
  self,
  ip_addr: String,
  fut: impl Future<Output = Result<T, E>>,
) -> Result<T, E>
where
  E: From<MyAnyhowError>,

However, the ? operators return the error below:

`?` couldn't convert the error to `E`

the trait `std::convert::From<anyhow::Error>` is not implemented for `E`

note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
note: required by `std::convert::From::from`

Is it possible to implement From for a generic that would satisfy that requirement?

This is the part I don't understand. Why do you want the function to return different errors to different callers? Why not return the same error type to all callers? The error type could be an enum, as you suggested above, or a type like Box<dyn std::error::Error>, depending on what the callers want to do with the error.

If you do need the error type to be caller-specified and have only a single bound on it, then you'll need to go through two conversions at each potential error site: First converting from the original error type to MyAnyhowError, and then converting from MyAnyhowError to the caller-specified type E. Since the ? will only insert a single conversion, you'd need to do the second conversion explicitly in the body of wrap.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.