Downcasting Rc<A> to Rc<B> if A == B

94 Views Asked by At

I would like to know if the following code is a valid use (maybe there is a builtin/safe way to downcast Rc<dyn SomeTrait> to Rc<SomeType>? I couldn't find any) and most importantly, is it safe?

use std::any::*;
use std::rc::*;

// NOTE: apparently adding Any here is REQUIRED
//       otherwise it doesn't work at all,
//       I have no idea why
trait SomeTrait: Any {}
struct SomeType;

impl SomeTrait for SomeType {}

fn rc_downcaster<A: 'static, B: ?Sized + 'static>(b: Rc<B>) -> Option<Rc<A>> {
    if Any::type_id(&*b) == TypeId::of::<A>() {
        Some(unsafe {
            let p = Rc::into_raw(b);
            Rc::from_raw(p as *const A)
        })
    } else {
        None
    }
}


fn main() {
    let x: Rc<dyn SomeTrait> = Rc::new(SomeType);
    let _: Rc<SomeType> = rc_downcaster(x).unwrap();
}

Playground

2

There are 2 best solutions below

1
Cecile On BEST ANSWER

Without using nightly, I found this safe alternative solution:

use std::any::*;
use std::rc::*;

trait SomeTrait: AsAny {}
struct SomeType;

impl SomeTrait for SomeType {}

trait AsAny {
    fn as_any(self: Rc<Self>) -> Rc<dyn Any>;
}

impl<T: 'static> AsAny for T {
    fn as_any(self: Rc<Self>) -> Rc<dyn Any> where Self: Sized
    {
        self
    }
}

fn main() {
    let x: Rc<dyn SomeTrait> = Rc::new(SomeType);
    let x: Rc<dyn Any> = x.as_any();
    let _: Rc<SomeType> = Rc::downcast(x).unwrap();
}

But it is worth mentioning the solution of @cafce25 (in comment):

You can use Rc::downcast if you are on nightly and add #![feature(trait_upcasting)]. Since that works your code should be sound as well. Playground

2
Matthieu M. On

Why is Any necessary?

Rust subscribes to a philosophy of explicit behavior.

This can be seen in a number of decisions -- such as nominal typing instead of structural typing -- and is reflected in the explicitness of function signatures. In particular, with generics, the set of possible operations on the generic type is encoded in the list of traits required for this generic type.

Unprincipled downcasting, that is the ability to always go back from super type to sub type, breaks that. A practical consequence is that it breaks wrapper types, for example, since now a function generic over T can have a different behavior based on whether I pass a String or a struct Wrapper(String) even if all the traits this function declares are implemented in the same way for both.

Therefore, Rust bans unprincipled downcasting in general and instead provides one escape hatch: Any. Any is a signal to the user that downcasting may be used, and therefore that the exact type may matter.

How to downcast, thus?

Well, by using Any!

#![feature(trait_upcasting)]

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

struct SomeType;

trait SomeTrait: Any {}

impl SomeTrait for SomeType {}

fn main() {
    let x: Rc<dyn SomeTrait> = Rc::new(SomeType);

    let x: Rc<dyn Any> = x; // trait_upcasting necessary here.

    let _: Rc<SomeType> = Rc::downcast(x).unwrap();
}

All 'static types implement Any.

Why 'static?

Lifetimes are pure compile-time constructs, and are thus not tracked by Any, so allowing any lifetime would lead to soundness holes. Restricting to only 'static was thus a conservative "known-sound" choice.

It's possible that an Any<'a> trait could be developed in the future; it gets tricky enough with lifetime variance (and covariance) that I couldn't say either way.