How does type coercion for Box<dyn Error> work?

Hi, I'm quite new to Rust and have some difficulties wrapping my head around type coercion.

Let's take the following code:


#[derive(Debug)]
enum CustomError {
    NotFound,
}

impl Error for CustomError {}

impl Display for CustomError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Some custom error")
    }
}

fn get_static_error() -> Result<(), Box<CustomError>> {
    Err(Box::new(CustomError::NotFound))
}

fn get_dynamic_error() -> Result<(), Box<dyn Error>> {
    Err(Box::new(CustomError::NotFound))
}

fn get_error(is_dynamic: bool) -> Result<(), Box<dyn Error>> {
    if is_dynamic {
        let dynamic_res = get_dynamic_error()?;
        return Ok(dynamic_res);
    }

    let static_res = get_static_error()?;
    Ok(static_res)
}

fn main() {
    let dynamic_error = get_error(true);

    match dynamic_error {
        Err(err) => {
            if let Some(err) = err.downcast_ref::<CustomError>() {
                println!("It is working!");
            }
        }
        _ => {}
    }

    let static_error = get_error(false);

    match static_error {
        Err(err) => {
            if let Some(err) = err.downcast_ref::<CustomError>() {
                println!("It is not working!");
            }
        }
        _ => {}
    }
}

We have a custom error CustomError that implements the trait Error. We also have two different functions: get_static_error that returns an error with type Box<CustomError> and get_dynamic_error that returns an error with type Box<dyn Error>. These two functions are called in a function get_error that also returns an error with type Box<dyn Error>.

The code complies just fine, but when we try to downcast the error to the CustomError type, it never works for the get_error(false) function call that uses get_static_error.

I can't understand why it happens, so it would be great if someone could explain why it happens.
Many thanks :slight_smile:

The implicit conversion from other error types to Box<dyn Error> is defined by the From trait, specifically this implementation:

impl<'a, E> From<E> for Box<dyn Error + 'a>
where
    E: Error + 'a,

due to the Error trait being lifted around Box with this impl of impl<T: Error> Error for Box<T>, the conversion works out that Box<CustomError> converts to Box<dyn Error> by adding an additional layer of boxing! So with your current code, something like

err.downcast_ref::<Box<CustomError>>()

would work instead; however, to prevent the double-boxing to happen in the first place, you probably instead want to change get_static_error to fn() -> Result<(), CustomError>

3 Likes

Great, thanks for the explanation!

I have another question.

How does the conversion work for get_dynamic_error? Why doesn't it generate double-wrapped box as well?

get_dynamic_error returns Box::new(CustomError::NotFound) that should have type Box<CustomError> that needs to be converted to Box<dyn Error>

This is called unsizing coercion.

2 Likes

Great question! The answer is that the standard library left out a little bit of "space" in that generic From implementation: the type parameter T: Error doesn't have a ?Sized annotation in the impl of Error for Box<T>, which limits it's to type with static size and thus excludes, among other things, Box<dyn Error>.

The From implementation that works here then isn't the generic From<E> for Box<dyn Error> but instead the reflexive From<T> for T. This is quite tricky API design; indeed without this trick, the standard library would run into overlapping impl errors. It's not "ideal", because it also rules out different un-Sized types from the generic Error for Box<E> and also it might be somewhat surprising that Box<dyn Error> itself dors not implement the Error trait, but it's pragmatic so IMO it's a good compromise.

Ah, I see, okay perhaps the above explanation doesn't quite match your question actually. That one is indeed explained as @HJVT did, as a coercion, no From implementation is involved here. (The From trait only comes into play with the ?-operator.)

My explanation above was addressing why the cal to get_dynamic_error()? with the ? doesn't are another layer of Box for the Box<dyn Error> case.

1 Like

Great, thanks for your answers!

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.