I was first expecting the answer to be "absolutely not!", Any doesn't help if you don't know the concrete type. But it turns out you can... with caveats, and I'm not 100% sure its totally safe.
To go from Rc<T> to Rc<U>, you can use the escape hatches into_raw and from_raw. The docs of the former read:
Constructs an Rc from a raw pointer.
The raw pointer must have been previously returned by a call to Rc<U>::into_raw where U must have the same size and alignment as T. This is trivially true if U is T. Note that if U is not T but has the same size and alignment, this is basically like transmuting references of different types. See mem::transmute for more information on what restrictions apply in this case.
The user of from_raw has to make sure a specific value of T is only dropped once.
This function is unsafe because improper use may lead to memory unsafety, even if the returned Rc<T> is never accessed.
With that in mind, since we only have access to TraitA, it'll need an as_b() function to get itself as a TraitB. The fact that the target is a super trait doesn't really help. Then we can write a crosscast function like so:
use std::rc::Rc;
trait TraitA {
fn print_a(&self);
// SAFETY: the resulting `dyn TraitB` must have the *exact* same address,
// size, alignment, and drop implementation for `crosscast` to work safely.
// Basically it must be `self` or maybe a transparently wrapped object.
unsafe fn as_b(&self) -> Option<&(dyn TraitB + 'static)>;
}
trait TraitB {
fn print_b(&self);
}
fn crosscast(a: Rc<dyn TraitA>) -> Option<Rc<dyn TraitB>> {
unsafe {
let b_ptr = a.as_b()? as *const dyn TraitB;
let a_ptr = Rc::into_raw(a);
// sanity check
assert!(a_ptr as *const () == b_ptr as *const ());
Some(Rc::from_raw(b_ptr))
}
}
With this function at our disposal, your problem becomes trivial by using .filter_map():
struct Both {
data: String,
}
impl TraitA for Both {
fn print_a(&self) { println!("A: {}", self.data); }
unsafe fn as_b(&self) -> Option<&(dyn TraitB + 'static)> { Some(self) }
}
impl TraitB for Both {
fn print_b(&self) { println!("B: {}", self.data); }
}
struct OnlyA {
data: String,
}
impl TraitA for OnlyA {
fn print_a(&self) { println!("A: {}", self.data); }
unsafe fn as_b(&self) -> Option<&(dyn TraitB + 'static)> { None }
}
fn main() {
let vec_a = vec![
Rc::new(Both{ data: "both".to_owned() }) as Rc<dyn TraitA>,
Rc::new(OnlyA{ data: "only a".to_owned() })
];
for a in &vec_a {
a.print_a();
}
println!();
let vec_b = vec_a
.into_iter()
.filter_map(crosscast)
.collect::<Vec<_>>();
for b in &vec_b {
b.print_b();
}
}
See it all together on the playground.
I would still recommend not doing this if at all possible. It would be perfectly safe for example to go from &Rc<dyn TraitA> to Option<&dyn TraitB> using the above method without all the restrictions. Something like this wouldn't have the restrictions and unsafety:
for b in vec_a.iter().filter_map(|a| a.as_b()) {
// ...
}