Tower Buffer and anyhow

Hi,

I have a custom tower::Service that I have wrapped in a tower::buffer::Buffer (in order to make it Clone such that I can issue calls to the same underlying service from multiple threads).

My customer tower::Service uses anyhow::Error as it's Error type (and I heavily use anyhow::Context) - and I would like to propagate this Error to the caller of my service.

However, tower::buffer::Buffer seems to always use a .into() on the Error to force it into a boxed error (Box) - and that conversion seems to prevent me from getting back the underlying anyhow::Error regardless of what I do (In particular I am gettinga ll kinds of error if trying to dowcast the type-erased Box back to anyhow::Error - I assume the reason is since its no longer an anyhow::Error but converted by the into() call in Buffer).

Does anyone have any simple suggestion for how to get buffer::Buffer to retuturn an anyhow::Error?

Thanks

Turning a anyhow::Error into a Box<dyn std::error::Error> with .into() should not be possible. anyhow::Error doesn't implement std::error::Error.

Can you show the code you used?

Into Box<dyn Error> is implemented.

Error::downcast should be able to extract anyhow::Error, but anyhow error may be wrapped in other error types too. Try debug printing {:?} to see how many layers you need to unpeel.

I think it is here tower/tower/src/buffer/worker.rs at master · tower-rs/tower (github.com) that the buffer converts the error

Hmm,

Thats strange - I'm not even getting it to compile since rustc complains:

error[E0277]: the trait bound `anyhow::Error: std::error::Error` is not satisfied
    --> test\src\lib.rs:125:44
     |
125  |             let e: anyhow::Error = match e.downcast() {
     |                                            ^^^^^^^^ the trait `std::error::Error` is not implemented for `anyhow::Error`
     |
note: required by a bound in `boxed::<impl (dyn std::error::Error + std::marker::Send + Sync + 'static)>::downcast`        
    --> toolchains\nightly-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\alloc\src\boxed.rs:2204:24
     |
2204 |     pub fn downcast<T: Error + 'static>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
     |                        ^^^^^ required by this bound in `boxed::<impl dyn Error + Send + Sync>::downcast`

The error suggests that e is already anyhow::Error?

Hmm, no, the following code generates that error:

 let r = r.map_err(|e| {
            let e: Box<dyn std::error::Error> = e;
            eprintln!("{:?}", e);
            let e: anyhow::Error = match e.downcast() {
                Result::Ok(a) => *a,
                Err(e) => anyhow!("simple error: {}", e),
            };
            anyhow!("{}", e)
        })?;

That 3rd line shows that e is not allready anyhow::Error byt a type-erased box?

Use e.downcast::<anyhow::Error>() to avoid relying on type inference.

error[E0277]: the trait bound `anyhow::Error: std::error::Error` is not satisfied
    --> src\lib.rs:127:55
     |
127  |             let e: anyhow::Error = match e.downcast::<anyhow::Error>() {
     |                                            --------   ^^^^^^^^^^^^^ the trait `std::error::Error` is not implemented for `util::Error`
     |                                            |
     |                                            required by a bound introduced by this call

Same problem, doesnt seem to be the type inference that's the problem

(util::Error = anyhow::Error, I have pub use anyhow::Error in util)

Aahhhh, I see: downcast has <T: Error + 'static> where T: std::error::Error for some reason applies to the return type.

Note sure i understood, bute here seems to be a minimal example:

I mean it's not possible to downcast dyn Error to anyhow::Error, because the Error::downcast method says it can only return types implementing std Error, and anyhow::Error doesn't implement it.
This limitation seems arbitrary to me, but that's how it's implemented. The conversion from anyhow seems to be one-way only.

arghh :frowning:

Ok thansk - and I assume thatt here is no way to use the downcast methid in Box either? (Eg. not the error downcast) - I ahve tried that but keeps running into various errors with that too..

The other downcast requires Any bound.

My bad. I read it the other way around. (Box<dyn Error> -> anyhow::Error)

The conversion from anyhow::Error to Box<dyn Error> is implemented as

impl From<Error> for Box<dyn StdError + Send + Sync + 'static> {
    #[cold]
    fn from(error: Error) -> Self {
        let outer = ManuallyDrop::new(error);
        unsafe {
            // Use vtable to attach ErrorImpl<E>'s native StdError vtable for
            // the right original type E.
            (vtable(outer.inner.ptr).object_boxed)(outer.inner)
        }
    }
}

which calls

unsafe fn object_boxed<E>(e: Own<ErrorImpl>) -> Box<dyn StdError + Send + Sync + 'static>
where
    E: StdError + Send + Sync + 'static,
{
    // Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
    let unerased_own = e.cast::<ErrorImpl<E>>();
    unsafe { unerased_own.boxed() }
}

In other words I think you have to downcast into the type of the error that is stored inside the anyhow::Error rather than downcast to anyhow::Error itself. Some constructors of anyhow::Error store a private type as inner error however like .context() which stores the private DisplayError type.

Thansk, that's interesting - but for me as I understand it means the same since the errors I'm interested in are those with context whcih are then private types so unsable to me anyway?

So I feel a bit supid - but thought I should not it down for potential googlers:

My initial issue was that I wanted to propagate the Contexts associated with the anyhow:Error across the tower::Service boundary - i.e. the error being converted Into a Box.

Turns out that anyhow supports to convert back into an anyhow error including the contexts simply by .map_err(|e| anyhow!(e)) on the recieving side :slight_smile:

Previously I had been using anyhow!("{}", e) which obviously formats the error and thus loses the contexts - but learning about the anyhow!(e) form of the macros solves all problems..

/V

1 Like

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.