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
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 changeget_static_error to fn() -> Result<(), CustomError>
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.