Rc<dyn AnimalT> -> Option<Rc<Cat>>

Supose we have:

pub trait AnimalT {}

pub struct Cat {}
pub struct Dog {}

foo(x: Rc<dyn AnimalT>) -> Option<Rc<Cat>> {
  // I thought this is called a 'dynamic cast'
  // but I can't find sample code for it
}

How do we, dynamically, at run time, say: see if we can cast this AnimalT into a Cat ?

The methods for doing this in Rust are usually named downcast or similar.

1 Like

You have to go through the Any trait. As far as I remember, this is the easiest way:

use std::any::Any;
use std::rc::Rc;

pub trait AnimalT: AnimalToAny {}

pub trait AnimalToAny: Any {
    fn to_any(self: Rc<Self>) -> Rc<dyn Any>;
}
impl<T: AnimalT> AnimalToAny for T {
    fn to_any(self: Rc<Self>) -> Rc<dyn Any> {
        self
    }
}

pub struct Cat {}
pub struct Dog {}

impl AnimalT for Cat {}
impl AnimalT for Dog {}

fn foo(x: Rc<dyn AnimalT>) -> Option<Rc<Cat>> {
    let rc_any = x.to_any();
    rc_any.downcast().ok()
}
3 Likes

Thanks @alice !

Pedantic question: can we get rid of the AnimalToAny and directly to AnimalT: Any ? I'm trying to understand if there is something I am missing here.

Not without a lot of trouble (and maybe some unsafe). All of the downcast methods work on references to dyn Any objects, so you need to provide a way to get one of those from a dyn AnimalT. If you try to do it directly, you’ll run into problems with Sized, I think.

If you get rid of the helper trait, you have to define the to_any method on every implementor of the trait, rather than as a single blanket impl. It's cumbersome, but would work.

1 Like

The extra trait doesn’t have to be specific to AnimalT, though. You could define something like this:

pub trait Reinterpret<T:?Sized> {    
    fn cast_rc(self: Rc<Self>)->Rc<T>;
    fn cast_ref(&self)->&T;
    fn cast_mut(&mut self)->&mut T;
    fn cast_box(self: Box<Self>)->Box<T>;
}

impl<T:Any> Reinterpret<dyn Any> for T {
    fn cast_rc(self: Rc<Self>)->Rc<dyn Any> { self }
    fn cast_ref(&self)->&dyn Any { self }
    fn cast_mut(&mut self)->&mut dyn Any { self }
    fn cast_box(self: Box<Self>)->Box<dyn Any> { self }
}

And then any trait that needs this functionality in its VTable can depend on Reinterpret<dyn Any>:

pub trait AnimalT: Reinterpret<dyn Any> {}

Thanks, there is still something I do not understand:

use std::rc::Rc;
use core::any::Any;

pub trait Reinterpret<T: ?Sized> {
    fn cast_rc(self: Rc<Self>) -> Rc<T>;
}

impl<T: Any> Reinterpret<dyn Any> for T {
    fn cast_rc(self: Rc<Self>) -> Rc<dyn Any> {
        self
    }
}

pub trait AnimalT: Reinterpret<dyn Any> {}

pub struct Cat {}

impl AnimalT for Cat {}

fn main () {
    let x = Rc::new(Cat {});
    let y = x as Rc<dyn AnimalT>;
    let z = y.cast_rc();
    let z_bad = y as Rc<dyn Any>;
}

What is the difference between the "y.cast_rc()" and "y as Rc" so that the former works but the latter fails ?

as can only translate from a concrete type to a trait object, and never from a trait object.

What we’re doing with the trait is defining a method on each of the concrete types that does an as cast. Those methods then get stored in the virtual method table when the dyn AnimalT is constructed so that the compiler knows which one to call in a dynamic context.

1 Like

To summarize:

  1. You wanted to perform downcasting.

  2. The built-in way to perform it is through the Any trait.

  3. For technical reasons, only an exact dyn Any can be downcasted: if you have a "sub"-trait Animal : Any, there is no implicit conversion / coercion from dyn Animal to dyn Any, not even behind pointers.

  4. Such coercion is called upcasting, and despite it not being built-in, there is a trick to have it: bundle the upcasting logic within the "sub"-trait as helper methods (e.g., cast_rc, etc.), and now you can upcast.

    So we'd like to write:

    trait Animal : Any {
        /* `Animal`-specific methods */
    
        /// Provide the default obvious implementation
        fn cast_rc (self: Rc<Self>) -> Rc<dyn Any>
        {
            self as Rc<dyn Any> /* ...? */
        }
    }
    
  5. But another technical limitation does not let us write this that simply:

    • to coerce self: Rc<Self> to a Rc<dyn Any>, we need Self to be a concrete Sized type:

        fn cast_rc (self: Rc<Self>) -> Rc<dyn Any>
      + where
      +     Self : Sized,
        {
            self as Rc<dyn Any>
        }
      
    • to have cast_rc be callable / available on self: Rc<dyn Animal>, we cannot have a where Self : Sized bound:

        fn cast_rc (self: Rc<Self>) -> Rc<dyn Any>
      - where
      -     Self : Sized,
      - {
      -     self as Rc<dyn Any>
      - }
      + ;
      

    The general solution for this, in the future, will be partial impls, misnomed / miswritten as default impl, and misplaced inside the specialization feature:

    trait Animal : Any {
        /* Animal methods */
    
        fn cast_rc (self: Rc<Self>) -> Rc<dyn Any>
        ;
    }
    
    partial
    impl<T /* : Sized */> Animal for T {
        fn cast_rc (self: Rc<Self>) -> Rc<dyn Any>
        {
            self as Rc<dyn Any>
        }
    }
    
    Current specialization syntax
    + #![feature(specialization)]
    +
      trait Animal : Any {
          /* Animal methods */
    
          fn cast_rc (self: Rc<Self>) -> Rc<dyn Any>
          ;
      }
    
    - partial
    + default
      impl<T /* : Sized */> Animal for T {
          fn cast_rc (self: Rc<Self>) -> Rc<dyn Any>
          {
              self as Rc<dyn Any>
          }
      }
    

    On stable Rust, this cannot be done directly, but can be achieved through a helper trait, as showcased by @alice and @2e71828.


Alternative solution

If you only want to downcast for a very specific type, you can drop the whole Any ordeal altogether:

trait Animal {
    /* Animal methods */

    fn try_downcast_cat_rc (self: Rc<Self>)
      -> Result<Rc<Cat>, Rc<Self>>
    {
        // default impl: Downcast fails.
        Err(self)
    }
}

and then, for the impl Animal for Cat block, you add:

  impl Animal for Cat {
      /* Animal methods */
+
+     fn try_downcast_cat_rc (self: Rc<Self>)
+       -> Result<Rc<Cat>, Rc<Self>>
+     {
+         // `Cat` impl: downcast trivially succeeds.
+         Ok(self)
+     }
  }

This will allow you to write:

let animal: Rc<dyn Animal> = ...;
if let Ok(cat) = animal.try_downcast_cat_rc() {
    ...
}

With no Any, helper trait, upcsting, involved whatsoever :slightly_smiling_face: (but this of course only works with a finite set of downcast target types).

4 Likes