Using error_chain with JoinHandle

Viewed 256

I use error_chain for my error handling.

[dependencies]
error-chain = "0.12.2"

I have no problem when I'm only using a single-thread.

#[macro_use] extern crate error_chain;
error_chain! {}

fn main() -> Result<()> {
    crash_burn()
        .chain_err(|| "crash_burn() crashed and burned!")?;
    Ok(())
}

fn crash_burn() -> Result<()> {
    bail!("I'm an error.")
}

However, when trying to return an error from a thread I get a compilation error.

#[macro_use] extern crate error_chain;
error_chain! {}
use std::thread::*;

fn main() -> Result<()> {
    let child: JoinHandle<Result<()>> = spawn(move || {
        crash_burn()
            .chain_err(|| "crash_burn() crashed and burned!")?;
        Ok(())
    });

    let res = child.join()
        .chain_err(|| "Child thread panicked! This error did not come from crash_burn().")?;
    res // res contains the error from crash_burn()
}

Here is the compilation error.

error[E0599]: no method named `chain_err` found for enum `std::result::Result<std::result::Result<(), Error>, std::boxed::Box<(dyn std::any::Any + std::marker::Send + 'static)>>` in the current scope
   --> src/main.rs:13:10
    |
13  |         .chain_err(|| "Child thread panicked! This error did not come from crash_burn().")?;
    |          ^^^^^^^^^ method not found in `std::result::Result<std::result::Result<(), Error>, std::boxed::Box<(dyn std::any::Any + std::marker::Send + 'static)>>`

Why is this error not handled automatically?

2 Answers

Why is this error not handled automatically?

According to error_chain documentation

chain_err can be called on any Result type where the contained error type implements std::error::Error+Send + 'static

The error type in the Result of child.join() is Box<dyn Any> because a thread can panic with any value. For example the thread can do

panic!(78);

But std::boxed::Box<dyn Any> does not implement the Error trait (only error types implement that trait). However in order for err_chain to be called it needs to implement the Error trait. So that is why err_chain cannot be used.

@FlyingFish1414 was correct with why error_chain cannot be used directly with JoinHandle::join(), but I wanted to give a solution that allows chaining errors returned by threads.

fn chain_any(x: std::result::Result<std::result::Result<(), Error>, std::boxed::Box<dyn std::any::Any + std::marker::Send>>) -> Result<()> {
    match x {
        // Child did not panic, but might have thrown an error.
        Ok(x) => x.chain_err(|| "Child threw an error.")?,

        // Child panicked, let's try to print out the reason why.
        Err(e) => {
            println! {"{:?}", e.type_id()};
            if let Some(s) = e.downcast_ref::<&str>() {
                bail!(format!("Child panicked with this message:\n{}", s));
            } else if let Some(s) = e.downcast_ref::<String>() {
                bail!(format!("Child panicked with this message:\n{}", s));
            }
            bail!(format!("Child panicked! Not sure why, here's the panic:\n{:?}", e));
        }
    }
    Ok(())
}

Now just join the thread like such:

chain_any(child.join())

Try it out:

fn crash_burn() -> Result<()> {
    // panic!(4);
    // panic!("OH no a &str!");
    // panic!("OH no a {}", "String");
    // bail!("I'm an error.");
    Ok(())
}

Unfortunately we must check for every panic type individually if we want to print it out. There is no way (that I know of) to check if the Any returned by the child thread implements Display or Debug. According to the docs a &dyn Any is limited to testing whether a value is of a specified concrete type, and cannot be used to test whether a type implements a trait.

Related