Downcast Rc<Any> to Rc<T>

Is it possible to downcast an Rc<Any> to an Rc<T>?

Here's the background with as many details elided as possible; I hope it makes sense.

I have an enum which includes the following:

enum Datum {
    Int(MoltInt),
    Flt(MoltFloat),
    List(Rc<MoltList>),
    Other(Rc<MoltAny>),
    None,
}

where

trait MoltAny: Any + Display + Debug {
    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
    fn into_any(self: Box<Self>) -> Box<dyn Any>;
}

The idea is that I can stash a value of type T that implements Display and Debug as Datum::Other(Rc::new(value)); that allows Datum to be efficiently cloneable without duplicating data. But then I want to be able to downcast and return it as an Rc<T>:

pub fn as_other<T: 'static>(&self) -> Option<Rc<T>>
    where T: Display + Debug,
    {
   ...
   }

I actually have this working, but at present I have to nest the Rc's.

 from_other<T: 'static>(value: T) -> MoltValue
    where T: Display + Debug,
    {
        Datum::Other(Rc::new(Rc::new(value)))
    }

The outer Rc is so that the compiler knows that Datum::Other(Rc<MyAny>) is cloneable, and the inner so that I can downcast the MyAny to Rc<T>; then I can cheaply clone and return the Rc<T>.

It works, but I can't help thinking there's a better way to do it.

(The actual code is at https://github.com/wduquette/value/blob/master/src/value9.rs)

1 Like

Your link at the bottom is broken, it please remove the trailing .)

1 Like

OK, I hadn't seen the Rc::downcast method. However, what I've got is an Rc<MoltAny>, not a normal Rc<Any>. I'm using this call:

let result = (*other).downcast::<T>();

And I'm getting this compiler error.

no method named `downcast` found for type `std::rc::Rc<(dyn value10::MoltAny + 'static)>` in the current scope

It seems like maybe I could implement the downcast method for MoltAny, but I'm unsure how to do that (tried a couple of things; but I've not fully grokked traits yet). Any ideas?

This time the code is at https://github.com/wduquette/value/blob/master/src/value10.rs on line 237.

I also tried removing MoltAny and just using Any; the downcast works then (or, at least, it compiles); but I need Rc<MoltAny>::to_string, which I lose if I just use Any.

I think you will have to use some unsafe to do this, you can use Rc::downcast as a reference for what to do. Btw, if you click the [src] links in the docs it will take you to the source code!

Given the following trait signature:

trait MoltAny: Any + Display + Debug {
    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
    fn into_any(self: Box<Self>) -> Box<dyn Any>;
}

would it be a problem to do other.as_ref().as_any().downcast_ref::(),
assuming other is Rc ?

He wants to downcast to an Rc<T>, so that won't do it. You can do this to downcast your Rc<dyn MoltAny> into Rc<T> in the same way that Any does it.

3 Likes

I had not noticed that before, thanks for pointing it out.

I'll give your solution a try—the lovely thing about it is that it makes perfect sense to me, but it wouldn't have occurred to me to try it.

[Meta] I feel that way about many of the more esoteric (to me) :astonished: techniques that I see in this forum. For me, each such example shows both the power of Rust and the difficulty of really mastering the language.

3 Likes

This is working! I now use it like this:

    if let Datum::Other(other) = &*data_ref {
        // other is an &Rc<MoltAny>
        // Was: let result = (*other).downcast::<T>();
        let result = other.clone().downcast::<T>();

        if result.is_ok() {
            // Let's be sure we're really getting what we wanted.
            // Was: let out: Rc<T> = result.unwrap().clone();
            let out: Rc<T> = result.unwrap();
            return Some(out);
        }
    }

Before I was trying (*other).downcast::<T>(); but your example code expects to take ownership of the Rc<MoltAny>, so I had to clone it first. But then I didn't need to clone the downcast value before returning it, so I call that a big win.

Thank you very much!

if result.is_ok() {
    // Let's be sure we're really getting what we wanted.
    // Was: let out: Rc<T> = result.unwrap().clone(); 
    let out: Rc<T> = result.unwrap();
    return Some(out);
}

Could be

if let Ok(out) = result {
    return Some(out);
}
1 Like

I think there is a crate that does this, but I don't remember what it was.

An upcast from Rc<dyn Trait> to Rc<dyn Any> is possible:

use std::{rc::Rc, any::Any, fmt::Debug};

trait Interface: Any + Debug {
    fn as_any(&self) -> &dyn Any;
    fn as_any_rc(self: Rc<Self>) -> Rc<dyn Any>;
}

#[derive(Debug)]
struct Point {
    x: f64, y: f64
}
impl Point {
    fn new(x: f64, y: f64) -> Rc<dyn Interface> {
        Rc::new(Point{x,y})
    }
}
impl Interface for Point {
    fn as_any(&self) -> &dyn Any {self}
    fn as_any_rc(self: Rc<Self>) -> Rc<dyn Any> {self}
}

fn main() {
    let obj = Point::new(1.0,0.0);
    let p: Rc<Point> = obj.as_any_rc().downcast::<Point>().unwrap();
    println!("({}, {})",p.x,p.y);
}
1 Like

That's not really an upcast, and it relies on the user to give the default implementation instead of some other implementation. It also only works for the smart pointers that you specify, part of the reason it isn't really an upcast.

Well, whether it is an upcast or not, somehow depends on the exact definition of upcast, which is nowhere to be found.

Going from T where T: Trait + ?Sized to dyn Trait behind any smart pointer that implements CoerceUnsized/DispatchFromDyn is an upcast in Rust. Note that for now, we can't actually upcast unsized values, like [T] or str. So, what you showed was not an upcast from dyn MoltAny to dyn Any, but calling a method which upcasted the inner type.
This is a common workaround to actual upcasting. We even see the OP show it in the original post. The problem with this approach is that you can't control the implemention of into_any_rc, so the implementor could use any implementation. This can be solved by adding a new trait, but now you have a new trait to keep track of.

trait Interface: Any + Debug {
    ...
}
trait InterfaceExt: Interface {
    fn as_any(&self) -> &dyn Any;
    fn as_any_rc(self: Rc<Self>) -> Rc<dyn Any>;
}

impl<T: Interface> InterfaceExt for T {
    fn as_any(&self) -> &dyn Any { self }
    fn as_any_rc(self: Rc<Self>) -> Rc<dyn Any> { self }
}

But now you need to manage two traits.

Either way, going from Rc<dyn MoltAny> to Rc<dyn Any> isn't possible directly in the same way as going from Rc<i32> to Rc<dyn Any> is. You need to build some glue code to make it work.

1 Like

Ah ok, you mean one cannot simply state a function

fn upcast<dyn Trait>(p: Rc<dyn Trait>) -> Rc<dyn Any>
where Trait: Any

or more generally

fn upcast<dyn A, dyn B>(p: Rc<dyn A>) -> Rc<dyn B>
where A: B

Although from a theoretical point of view it might not be particularly complicated, since only the vtable pointer is exchanged?

Is harder than you think, especially when you want multiple parents and uou want to coerce to any of them. Then when you throw dylibs into the mix it becomes a mess.

See
https://github.com/rust-lang/rfcs/issues/2035

I just noticed this discussion. Fortunately, I've no need to go from Rc<MoltAny> to Rc<Any>. :slight_smile:

The question of upcasting appears regularly on this forum. The thread about my question answers some questions as well: Convert Box<dyn T> to Box<dyn Any>

Bottom line is: theoretically, this is possible. However, the details have not been settled yet and I believe that this will be part of the language in a few years.

The confusion about this topic comes from the fact that upcasting is a trivial operation in oop languages and is reqired for them to be useful. In rust, this is not the case.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.