anyhow icon indicating copy to clipboard operation
anyhow copied to clipboard

Pass anyhow::Error where impl Error is expected

Open hniksic opened this issue 4 years ago • 5 comments

I have a function that expects impl Error + Send + Sync + 'static:

fn a(_: impl std::error::Error + Send + Sync + 'static) {
    todo!()
}

I would like to call it with an instance of anyhow::Anyhow - for example, obtained through the anyhow! macro. I know that Anyhow doesn't implement std::error::Error, but I also know that it can be easily converted to Box<dyn Error>. However, for some reason, none of the usual tactics seem to work:

fn b() {
    a(anyhow!("foo"));        // the trait `StdError` is not implemented for `anyhow::Error`
}

fn b() {
    a(anyhow!("foo").into()); // cannot infer type for type parameter `impl std::error::Error`
}

fn b() {
    let err: Box<dyn std::error::Error + Send + Sync + 'static> = anyhow!("foo").into();
    a(err);    // expected an implementor of trait `StdError` - consider borrowing
}

fn b() {
    let err: Box<dyn std::error::Error + Send + Sync + 'static> = anyhow!("foo").into();
    a(&err);   // doesn't have a size known at compile-time
}

fn b() {
    let err: Box<dyn std::error::Error + Send + Sync + 'static> = anyhow!("foo").into();
    a(err.as_ref());  // borrowed value does not live long enough
}

Is there a way to box Anyhow so that the resulting object actually implements Error?

hniksic avatar May 06 '21 13:05 hniksic

If you need an Error trait impl, you need to use https://github.com/dtolnay/thiserror.

#[derive(Error, Debug)]
#[error(transparent)]
struct Error(#[from] anyhow::Error);

fn b() {
    a(Error(anyhow!("foo")));
}

dtolnay avatar May 06 '21 17:05 dtolnay

Sure, that would work - but it's quite unwieldy to define an Error struct without additional meaning. Since Anyhow is capable of transforming itself into Box<dyn Error>, I figured that an Error impl is lurking there internally and expected that it could be somehow obtained.

If there is a design principle that prevents this from working, then this issue can be closed. I'd also be curious to learn the reasoning behind it, and it might be interesting to others finding this through google.

hniksic avatar May 06 '21 19:05 hniksic

@hniksic the reason is that the implementation would be incoherent with the following two existing implementations:

  1. impl<E> From<E> for anyhow::Error where E: std::error::Error + Send + Sync + 'static
  2. impl From<T> for T provided by the standard library

The former is what gives you the ability to use ? on other errors.

You can try this for yourself

rossmacarthur avatar May 16 '21 15:05 rossmacarthur

@rossmacarthur That's indeed the reason why anyhow::Error doesn't itself implement std::error::Error. I was probably not clear enough, but my intention was to ask the more general question: why is there no way to get something that implements std::error::Error once you're in the possession of an anyhow::Error? After all, such a type must exist internally, otherwise Into<Boxed<dyn Error>> wouldn't be possible.

In the code that prompted this issue, the function accepting E: StdError did it with the best of intentions, its author reasoning that that would be the most generic way of accepting an error value (which is not entirely unreasonable). To call such a function, even an existential type would suffice - e.g. if anyhow::Error had a method into_error(self) -> impl StdError + Send + Sync + 'static, it would be perfect.

hniksic avatar May 16 '21 16:05 hniksic

So is there any simple way to pass anyhow::Error in a place which expects impl std::error::Error or has a from std::error::Error ?... Getting thiserror crate involved just for this seems like shooting a fly with bazooka...

let4be avatar Jun 14 '21 08:06 let4be

https://github.com/dtolnay/anyhow/issues/153#issuecomment-833718851 (or the equivalent handwritten Error impl) is the recommendation.

dtolnay avatar Nov 06 '22 12:11 dtolnay