Search code examples
rusttraitsdowncast

Cannot downcast logger back to original struct


I would like to access my log::Log impl instance in order to access a particular field, but once I receive the reference I am not able to downcast it to my struct.

I tried to convert the logger reference into core::any::Any and then downcasting it into my original struct, but I always obtain None.

use core::any::Any;

pub trait AToAny: 'static {
    fn as_any(&self) -> &dyn Any;
}

impl<T: 'static> AToAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

struct MyLogger {}

impl log::Log for MyLogger {
    fn enabled(&self, _: &log::Metadata<'_>) -> bool { todo!() }
    fn log(&self, _: &log::Record<'_>) { todo!() }
    fn flush(&self) { todo!() }
}

pub fn main() {
    let logger_impl = MyLogger {};
    log::set_boxed_logger(Box::new(logger_impl)).unwrap();
    let logger = log::logger();
    
    let logger_any = logger.as_any();
    let logger_impl = logger_any.downcast_ref::<MyLogger>()
        .expect("downcast failed");
}

I also tried without passing from log initialization functions, but I obtain the same result:

use core::any::Any; // 0.10.1

pub trait AToAny: 'static {
    fn as_any(&self) -> &dyn Any;
}

impl<T: 'static> AToAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

struct MyLogger {}

impl log::Log for MyLogger {
    fn enabled(&self, _: &log::Metadata<'_>) -> bool { todo!() }
    fn log(&self, _: &log::Record<'_>) { todo!() }
    fn flush(&self) { todo!() }
}

pub fn main() {
    let logger_impl = MyLogger {};
    let logger_boxed: Box<MyLogger> = Box::new(logger_impl);
    let logger: &'static mut dyn log::Log = Box::leak(logger_boxed);
    
    let logger_any = logger.as_any();
    let logger_impl = logger_any.downcast_ref::<MyLogger>()
        .expect("downcast failed");
}

Here is the code on the playground.

I saw that the type_id of the logger variable is different from the type_id of the logger_impl so I believe that this is what is preventing me from doing the downcast, but I cannot understant how I am supposed to fix this.


Solution

  • You're calling <&dyn Log>::as_any, but you can only downcast that to &dyn Log, that is logger_any.downcast_ref::<&dyn log::Log>() returns Some, but it gets you nowhere. A &dyn Log does not expose the information necesary to downcast to MyLogger.

    Fortunately you don't need Any or downcasting at all here since you can set the gobal logger with only a shared reference and still access the logger itself since shared references implement Copy:

    struct MyLogger {
        bar: &'static str,
    }
    impl MyLogger {
        fn foo(&self) {
            println!("foo")
        }
    }
    pub fn main() {
        let logger: &MyLogger = Box::leak(Box::new(MyLogger { bar: "baz"}));
        log::set_logger(logger as &dyn log::Log).unwrap(); // the cast is not needed, but illustrates what's happening implicitly otherwise.
        logger.foo(); // can still call inherent MyLogger::foo
        println!("{}", logger.bar); // and access fields.
    }