0

Let's say I have a trait for a Decoder, and I have specific decoders that implement this trait:

pub trait Decoder {
    fn do_something(&self) -> Result<(), DoSomethingError>;
}

pub enum DoSomethingError {}

pub struct SpecificDecoder1 {}

impl Decoder for SpecificDecoder1 {
    fn do_something(&self) -> Result<(), DoSomethingError> {
        todo!()
    }
}

pub struct SpecificDecoder2 {}

impl Decoder for SpecificDecoder2 {
    fn do_something(&self) -> Result<(), DoSomethingError> {
        todo!()
    }
}

each decoder could return specific errors. However, I don't want to include these specific errors in the DoSomethingError trait as some decoders could not even be available on some devices, so I don't want the mess of #cfg(feature="...") on the enum.

I thought about

pub trait DoSomethingError {}
fn do_something(&self) -> Result<(), Box<dyn DoSomethingError>> {
    Ok(self.do_another_something()?)
}

this way I can downcast the Box<dyn DoSomethingError> to a specific error if needed. However, Ok(self.do_another_something()?) wouldn't work. Remember that I don't want to store a string, I want to be able to match the error somehow, or try to match it, so I cannot create a UserDefined(String) variant on the string and convert every error to it.

1 Answer 1

1

If you want to have a different Error for each implementation, you can define the trait with an associated error type, just like in TryFrom:

pub trait Decoder {
    type Error;
    fn do_something(&self) -> Result<(), Self::Error>;
}

pub enum SpecificDecoder1Error {}
pub struct SpecificDecoder1 {}
impl Decoder for SpecificDecoder1 {
    type Error = SpecificDecoder1Error;
    fn do_something(&self) -> Result<(), Self::Error> {
        todo!()
    }
}

pub enum SpecificDecoder2Error {}
pub struct SpecificDecoder2 {}
impl Decoder for SpecificDecoder2 {
    type Error = SpecificDecoder2Error;
    fn do_something(&self) -> Result<(), Self::Error> {
        todo!()
    }
}

But if you want the trait to return a generic type, you can return a dyn std::any::Any value, and downcast if necessary:

pub trait Decoder {
    fn do_something(&self) -> Result<(), Box<dyn std::any::Any>>;
}

pub struct SpecificDecoder1Error(u16);
pub struct SpecificDecoder1 {}
impl Decoder for SpecificDecoder1 {
    fn do_something(&self) -> Result<(), Box<dyn std::any::Any>> {
        Err(Box::new(SpecificDecoder1Error(13)))
    }
}

pub struct SpecificDecoder2Error(String);
pub struct SpecificDecoder2 {}
impl Decoder for SpecificDecoder2 {
    fn do_something(&self) -> Result<(), Box<dyn std::any::Any>> {
        Err(Box::new(SpecificDecoder2Error(
            "Specific Error 2".to_string(),
        )))
    }
}

fn main() {
    let dec1 = SpecificDecoder1 {};
    let dec2 = SpecificDecoder2 {};
    let decs: [&dyn Decoder; 2] = [&dec1, &dec2];
    for dec in decs {
        let error = dec.do_something().err().unwrap();
        if let Some(error1) = error.downcast_ref::<SpecificDecoder1Error>() {
            println!("Error from SpecificDecoder1Error: {}", error1.0)
        } else if let Some(error2) =
            error.downcast_ref::<SpecificDecoder2Error>()
        {
            println!("Error from SpecificDecoder2Error: {}", error2.0)
        } else {
            unreachable!()
        }
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

my concern was about using ? on the dyn Any. Creating error boxes all the time out be bad. Do you think there's a nice way of not needing to create a box every time?
Yes, avoiding using box when unnecessary is a good idea. But it depends a lot about the citation. Usually errors are returned only once, because lots of times when they happen, the program abort it's execution/task, so maybe in you case is not a big deal.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.