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).
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 hatchesinto_raw
andfrom_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()) {
// ...
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.