Search code examples
rustdowncast

Lifetime error while attempting to downcast


I have a linked list of errors which I'm trying to traverse. Given MyError, which is a linked list of errors with an optional code, my goal is to traverse the chain and return the first non-None code:

type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;

#[derive(Debug)]
struct MyError {
    code: Option<u32>,
    source: Option<BoxedError>,
}

impl std::error::Error for MyError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.source().map(|s| s as _)
    }
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        std::fmt::Display::fmt("", f)
    }
}

impl MyError {
    fn source(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
        self.source.as_ref().map(|c| c.as_ref())
    }

    fn traverse(&self) -> Option<u32> {
        use core::any::Any;
        if let Some(code) = self.code {
            return Some(code);
        }    
        if let Some(source) = self.source() {
            let as_any = &source as &dyn Any;
            if let Some(myerr) = as_any.downcast_ref::<MyError>() {
                return myerr.traverse();
            }
        }
        None
    }
}

This gives the following error:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/lib.rs:31:36
   |
26 |     fn traverse(&self) -> Option<u32> {
   |                 ----- this data with an anonymous lifetime `'_`...
...
31 |         if let Some(source) = self.source() {
   |                               ---- ^^^^^^
   |                               |
   |                               ...is used here...
32 |             let as_any = &source as &dyn Any;
   |                          ------- ...and is required to live as long as `'static` here

For more information about this error, try `rustc --explain E0759`.
error: could not compile `playground` due to previous error

I'm not sure why I'd have a lifetime error here given that the source function returns a static trait object (which means that there are no other references behind that object, right?).


Solution

  • &source as &dyn Any doesn't do what you think. source is of type &(dyn std::error::Error + Send + Sync + 'static), so this is the type that must be erased to coerce &source to &dyn Any.

    The + 'static in this type only means that the referenced type, the one implementing Error + Send + Sync, must contain no non-'static references. The & still has its own lifetime, which is not 'static (and cannot be, since it is borrowed from self). It is the lifetime of this reference which prevents &(dyn std::error::Error + Send + Sync + 'static) from being 'static.

    What you meant to do was source as &dyn Any, which, if it worked, would upcast the dyn Error to dyn Any. But this is not possible because Any is not a supertrait of Error. Moreover, even if Error did have Any as a supertrait, Rust doesn't currently support such a cast.

    Trait upcasting is available on nightly under a feature flag, but you don't need that. Use the downcast methods defined on dyn Error itself, which are added to support this use case.

    if let Some(myerr) = source.downcast_ref::<MyError>() {
        return myerr.traverse();
    }
    

    If you need to downcast a trait object that is not dyn Error, you will need to find a workaround.