How to preserve error if it is the right type with <s>templating</s>generics

I have an error type that is coming from a template, which might (in fact probably will be) std::io::Error. Then I return a std::io::Error, if the original error is a std::io::Error, I would like to just return it, but if not, I want to wrap it using std::io::Error::new. I can do this using code that looks like:

let error: io::Error = match e.into().downcast::<io::Error>() {
    Ok(err) => *err,
    Err(err) => io::Error::new(io::ErrorKind::ConnectionAborted, err)
};

where e is constrained to be Into<Box<dyn Error + Send + Sync>>

but, it feels a little wrong to me that I have to Box the error, only to immediately move it out of the box (and free the memory it just allocated).

I can't think of a better way to do this though with current Rust. Although, I think there is a way that it could be done with static dispatch once RFC 1210 lands.

Is there a better way to handle this?

1 Like

What's the original type of e? How does it achieve being either an io::Error or something else, dynamically? AFAICT that is only possible if it is already a trait object to begin with. And in that case, it must already be behind a pointer (e.g. Box) in order to be passed by-value.

Incidentally, what do you mean by "templating"? Is the I/O error coming from a templating library? Or do you mean "generics", i.e. e is of some type T: Error? (Please do not call generics "templates". Rust generics are not even remotely similar to C++ templates.)

Can you constrain e to be Error + Send + Sync + 'static instead?

use any::Any;
use error::Error;
use std::*;

fn convert_err<E: Error + Send + Sync + 'static>(e: E) -> io::Error {
    let mut opt_err = Some(e);
    fn take_cast<In: Any, Out: Any>(opt: &mut Option<In>) -> Option<Out> {
        (opt as &mut dyn Any)
            .downcast_mut::<Option<Out>>()
            .map(|x| x.take())
            .flatten()
    }

    if let Some(err) = take_cast::<E, io::Error>(&mut opt_err) {
        err
    } else if let Some(boxed_io_err) = take_cast::<E, Box<io::Error>>(&mut opt_err) {
        *boxed_io_err
    } else if let Some(boxed_dyn_err) = take_cast::<E, Box<dyn Error + Send + Sync>>(&mut opt_err) {
        io::Error::new(io::ErrorKind::ConnectionAborted, boxed_dyn_err)
    } else {
        io::Error::new(
            io::ErrorKind::ConnectionAborted,
            Box::new(opt_err.take().unwrap()),
        )
    }
}

(Playground,compiles but untested)

4 Likes

What's the original type of e ? How does it achieve being either an io::Error or something else, dynamically?

The type of e is generic. So the type of e could actually be io::Error, or something else. I'd actually prefer to choose how to handle it statically, but I'm not sure how to do that, hence this topic.

Incidentally, what do you mean by "templating"?

I meant generics. (And incidentally, Rust generics are more similar to C++ templates than, say Go or Java generics, since Rust and C++ both use monomorphization).