简体   繁体   中英

How to filter RCed trait objects vector of specific subtrait in Rust?

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 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()) {
   // ...
}

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM