Dynamically check if a &dyn Vehicle is also a &dyn Car?

Suppose we have:

pub trait Vehicle {}
pub trait Car {}

Now, suppose a x: Rc<dyn Vehicle>. Is there a way to check if this x can also be a Rc<dyn Car> ?

I don't believe so, as dyn means the concrete type has been "erased" and at that point only the trait implementations listed after the dyn are known to be implemented by the type.

1 Like

If, instead of

pub trait Car {}

we had:

pub trait Car: Vehicle

would that make any difference ?

2 Likes

What you can do is:

pub trait Vehicle {
    fn is_car(&self) -> bool {
        false
    }
}
pub trait Car {}

fn main() {
    struct S;
    struct T;
    impl Vehicle for S { fn is_car(&self) -> bool { true } }
    impl Car for S {}
    impl Vehicle for T {}
    let s: Box<dyn Vehicle> = Box::new(S);
    let t: Box<dyn Vehicle> = Box::new(T);
    assert_eq!(s.is_car(), true);
    assert_eq!(t.is_car(), false);
}

(Playground)

2 Likes
  1. Giving credit where credit is due, you technically solved my problem as stated. :slight_smile:

  2. What I actually want (but failed to write) is a function of type signature:

fn foo(v: Rc<dyn Vehicle>) -> Option<Rc<dyn Car>>

This is my fault for not being clear in the original question.

I don't think this is possible. I thought on using Any, but I think this works only do downcast to specific types (from Any).


Maybe this:

pub trait Vehicle {
    fn downcast_car(&self) -> Option<&dyn Car> {
        None
    }
}
pub trait Car {}

fn main() {
    struct S;
    struct T;
    impl Vehicle for S { fn downcast_car(&self) -> Option<&dyn Car> { Some(self) } }
    impl Car for S {}
    impl Vehicle for T {}
    let s: Box<dyn Vehicle> = Box::new(S);
    let t: Box<dyn Vehicle> = Box::new(T);
    assert!(s.downcast_car().is_some());
    assert!(t.downcast_car().is_none());
}

(Playground)


Or with Rc<Self>:

use std::rc::Rc;

pub trait Vehicle {
    fn downcast_car(self: Rc<Self>) -> Option<Rc<dyn Car>> {
        None
    }
}
pub trait Car {}

fn main() {
    struct S;
    struct T;
    impl Vehicle for S { fn downcast_car(self: Rc<Self>) -> Option<Rc<dyn Car>> { Some(self.clone()) } }
    impl Car for S {}
    impl Vehicle for T {}
    let s: Rc<dyn Vehicle> = Rc::new(S);
    let t: Rc<dyn Vehicle> = Rc::new(T);
    assert!(s.downcast_car().is_some());
    assert!(t.downcast_car().is_none());
}

(Playground)

Requires redundant implementation of downcast_car though in either case, I think.

3 Likes

You want this:

pub trait Vehicle: Any {}
pub trait Car: Any /* or Vehicle */ {}

But I don't think Any allows downcasting to a dyn Trait, does it? If yes, could you show how?

1 Like

Oops, yes you're correct..

Another workaround, depending on your use case, might be to use enums:

use std::rc::Rc;

pub trait Vehicle {}
pub trait Car: Vehicle {}

#[derive(Clone)]
pub enum SpecificVehicle {
    General(Rc<dyn Vehicle>),
    Car(Rc<dyn Car>),
}

pub fn foo(v: SpecificVehicle) -> Option<Rc<dyn Car>> {
    match v {
        SpecificVehicle::General(_) => None,
        SpecificVehicle::Car(car) => Some(car),
    }
}

fn main() {
    struct S;
    struct T;
    impl Vehicle for S {}
    impl Car for S {}
    impl Vehicle for T {}
    let vec: Vec<SpecificVehicle> = vec![
        SpecificVehicle::Car(Rc::new(S)),
        SpecificVehicle::General(Rc::new(T)),
    ];
    assert!(foo(vec[0].clone()).is_some());
    assert!(foo(vec[1].clone()).is_none());
}

(Playground)

You can get rid of the downcast_car definitions.

4 Likes

The only downside is that I need to provide an impl MaybeCar for X {} for any type X which is not a Car, right? (Line #26 in your Playground.)

But there is no function body needed in that implementation! :ok_hand:


I tried to fix this with specialization, but it doesn't work:

#![feature(specialization)]

use std::rc::Rc;

pub trait MaybeCar {
    fn downcast_car(self: Rc<Self>) -> Option<Rc<dyn Car>>;
}

default impl<T> MaybeCar for T {
    fn downcast_car(self: Rc<Self>) -> Option<Rc<dyn Car>> {
        None
    }
}

impl<T: Car + 'static> MaybeCar for T {
    fn downcast_car(self: Rc<Self>) -> Option<Rc<dyn Car>> {
        Some(self)
    }
}

pub trait Vehicle: MaybeCar {}
pub trait Car: Vehicle {}

fn main() {
    struct S;
    struct T;
    impl Vehicle for S {}
    impl Car for S {}
    //impl Vehicle for T {}

    let s: Rc<dyn Vehicle> = Rc::new(S);
    let t: Rc<dyn Vehicle> = Rc::new(T);
    assert!(s.downcast_car().is_some());
    assert!(t.downcast_car().is_none());
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
 --> src/main.rs:1:12
  |
1 | #![feature(specialization)]
  |            ^^^^^^^^^^^^^^
  |
  = note: see issue #31844 <https://github.com/rust-lang/rust/issues/31844> for more information
  = help: consider using `min_specialization` instead, which is more stable and complete
  = note: `#[warn(incomplete_features)]` on by default

error[E0277]: the trait bound `T: Vehicle` is not satisfied
  --> src/main.rs:32:30
   |
32 |     let t: Rc<dyn Vehicle> = Rc::new(T);
   |                              ^^^^^^^^^^ the trait `Vehicle` is not implemented for `T`
   |
   = help: the trait `Vehicle` is implemented for `S`
   = note: required for the cast from `T` to the object type `dyn Vehicle`

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

What am I doing wrong? Or is this a problem of specialization being imcomplete? (I didn't use it really yet.)

If you have a closed set of destination types that all implement the target trait, you can do it like this:

macro_rules! rc_any_downcast {
    ($var:expr => $first:ty $(| $rest:ty)*) => {{
        let rc = ($var).as_rc_any();
        if let Ok(x) = Rc::downcast::<$first>(Rc::clone(&rc)) { Some(x) }
        $(
            else if let Ok(x) = Rc::downcast::<$rest>(Rc::clone(&rc)) { Some(x) }
        )*
        else { None }
    }}
}

pub trait RcAny {
    fn as_rc_any(self: Rc<Self>)->Rc<dyn Any>;
}

impl<T:Any> RcAny for T {
    fn as_rc_any(self: Rc<T>)->Rc<dyn Any> { self }
}

pub trait Vehicle: RcAny {
    fn downcast_car(self: Rc<Self>) -> Option<Rc<dyn Car>> {
        // All supported car types:
        rc_any_downcast!(self => S | U)
    }
}
1 Like

I tried to understand your example, but it's pretty hard for me.

I think there is some unnecessary cloning happening. Maybe this could be solved with downcast_ref and cloning later?

Right.

I haven't played with specialization much myself either, and couldn't get it to work with min_specialization (but didn't try too hard either). However I do know that non-min-specialization is unsound.

Basically, it's calling Rc::downcast::<T>(...) in turn for each possible concrete type T until it finds the one that matches, and then casts that to a Rc<dyn Car>. The only thing that's being cloned is the outer Rc, because Rc::downcast takes ownership of its argument.