Use-case
I'm writing a code-generator for an IDL in which users can define their own exception types with custom fields. The IDL does not require these user-defined exception types to share any common fields or methods. I want the rust representation of these types to be wrapped in my crate's common Error
type.
Here's what I came up with:
pub enum Error {
/// Predefined error type for protocol-level issues.
Protocol(ProtocolError),
/// Should hold any user-defined types.
UserDefined(Box<error::Error + Sync + Send>),
}
Now, obviously, users want to unpack the value contained by UserDefined
as the custom type they defined and evaluate its fields and execute its methods. I read that you can 'downcast' to a specific type by using Any
.
Unfortunately I can't figure out how exactly to use it with the definition above! Here's some non-working example code demonstrating what I'm trying to achieve, as well as a playground link.
use std::any::Any;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::error;
// predefined
pub enum Error {
Protocol(ProtocolError),
UserDefined(Box<error::Error + Sync + Send>),
}
// some standard protocol error type (known in advance)
pub struct ProtocolError {
message: String,
}
// users will define these error types
// and impl. StdError, Display, Debug
#[derive(Debug)]
pub struct MyError {
message: String,
}
impl error::Error for MyError {
fn description(&self) -> &str {
&self.message
}
}
impl Display for MyError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "this is my error and here's what caused it:{}", self.message)
}
}
// attempt to figure out the exact type of
// the wrapped error and do something with it
fn main() {
let e0 = Error::UserDefined("TEST".to_owned().into());
let e1 = Error::UserDefined(Box::new(MyError { message: "oh noes!".to_owned() }));
do_work(&e0);
do_work(&e1);
}
fn do_work(e: &Error) {
match *e {
Error::UserDefined(ref e) => {
let wrapped: &Box<error::Error + Send + Sync> = e;
let wrapped = &**wrapped as &Any;
if let Some(m) = wrapped.downcast_ref::<String>() {
println!("was a String {}!", m);
}
if let Some(x) = wrapped.downcast_ref::<MyError>() {
println!("was a MyError {}!", x);
}
}
_ => (),
}
}
This code fails with non-scalar cast: '&std::error::Error + Send + Sync' as '&std::any::Any + 'static'
, which kinda makes sense - I've explicitly cast it to the type contained by the Box
, but I thought that all types implicitly implemented Any
.
Is what I'm doing possible? And if so, is the approach above the best way to achieve it?