Reputation: 20408
The task is to filter out the supertrait objects for base trait object vector:
use std::rc::Rc;
use std::any::Any;
pub trait TraitA {
fn get_title(&self) -> &str;
fn as_any(&self) -> Any;
}
pub trait TraitB: TraitA {
fn get_something(&self) -> &str;
}
pub fn filter_b(input: Vec<Rc<dyn TraitA>>) -> Vec<Rc<dyn TraitB>> {
// bs.filter(|it| /* How to do it? */).collect();
}
Is it even possible? Any clue or advice?
I know as_any()
can be used to downcast but as i'm not sure how it's meant to work with Rc
as it takes ownership (and thus requires the instance).
Upvotes: 2
Views: 338
Reputation: 60022
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
whereU
must have the same size and alignment asT
. This is trivially true ifU
isT
. Note that ifU
is notT
but has the same size and alignment, this is basically like transmuting references of different types. Seemem::transmute
for more information on what restrictions apply in this case.The user of
from_raw
has to make sure a specific value ofT
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()) {
// ...
}
Upvotes: 2