Search code examples
rustlifetimedowncast

Downcasting a type with a named lifetime parameter


I have the following code:

use std::any::Any;
use std::borrow::Cow;

// Base trait for downcasting
pub trait AsTranslationUnit<'a> {
    fn as_any(&self) -> &dyn Any;
}

pub trait TranslationUnit<'a>: AsTranslationUnit<'a> {
    fn file_stem(&self) -> &Cow<'a, str>;
}

// Implementation of AsTranslationUnit for all types implementing TranslationUnit
impl<'a, T: TranslationUnit<'a> + 'a> AsTranslationUnit<'a> for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct ModuleInterfaceModel<'a> {
    pub file_stem: Cow<'a, str>,
}

impl<'a> TranslationUnit<'a> for ModuleInterfaceModel<'a> {
    fn file_stem(&self) -> &Cow<'a, str> {
        &self.file_stem
    }
}

fn main() {
    let model = ModuleInterfaceModel {
        file_stem: Cow::Borrowed("example"),
    };

    let tu: &dyn TranslationUnit = &model;

    if let Some(mi) = tu.as_any().downcast_ref::<ModuleInterfaceModel>() {
        println!("Module Interface: {:?}", mi);
    } else {
        println!("Not a Module Interface");
    }
}

Which produces the following error:

   Compiling playground v0.0.1 (/playground)
error[E0310]: the parameter type `T` may not live long enough
  --> src/main.rs:16:9
   |
16 |         self
   |         ^^^^
   |         |
   |         the parameter type `T` must be valid for the static lifetime...
   |         ...so that the type `T` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
14 | impl<'a, T: TranslationUnit<'a> + 'a + 'static> AsTranslationUnit<'a> for T {
   |                                      +++++++++

For more information about this error, try `rustc --explain E0310`.
error: could not compile `playground` (bin "playground") due to 1 previous error

I would like to downcast any type of TranslationUnit to their original type, but all of them contains named lifetime parameters, so I can't apply this approach successfully.

What would be the more idiomatic approach to solve this without 'static lifetimes extra bounds (which obviously doesn't solve my problem) and without get riding out of the named lifetime parameter in my structs?


Solution

  • Any does not support lifetimes. Period.

    There are some crates supporting lifetimed Any, such as transient, but I can't vouch for their soundness as I haven't reviewed them.

    Here's an example using it:

    use std::borrow::Cow;
    
    use transient::{Any, Downcast, Inv, Transient};
    
    // Base trait for downcasting
    pub trait AsTranslationUnit<'a> {
        fn as_any(&self) -> &dyn Any<Inv<'a>>;
    }
    
    pub trait TranslationUnit<'a>: AsTranslationUnit<'a> + Any<Inv<'a>> {
        fn file_stem(&self) -> &Cow<'a, str>;
    }
    
    // Implementation of AsTranslationUnit for all types implementing TranslationUnit
    impl<'a, T: TranslationUnit<'a> + 'a> AsTranslationUnit<'a> for T {
        fn as_any(&self) -> &dyn Any<Inv<'a>> {
            self
        }
    }
    
    #[derive(Debug, PartialEq, Eq, Clone, Default, Transient)]
    pub struct ModuleInterfaceModel<'a> {
        pub file_stem: Cow<'a, str>,
    }
    
    impl<'a> TranslationUnit<'a> for ModuleInterfaceModel<'a> {
        fn file_stem(&self) -> &Cow<'a, str> {
            &self.file_stem
        }
    }
    
    fn main() {
        let model = ModuleInterfaceModel {
            file_stem: Cow::Borrowed("example"),
        };
    
        let tu: &dyn TranslationUnit = &model;
    
        if let Some(mi) = tu.as_any().downcast_ref::<ModuleInterfaceModel>() {
            println!("Module Interface: {:?}", mi);
        } else {
            println!("Not a Module Interface");
        }
    }