如何过滤 Rust 中特定子特征的 RCed 特征对象向量?

问题描述

任务是为基本特征对象向量过滤掉超特征对象:

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();
}

有可能吗?有什么线索或建议吗?

我知道 as_any() 可以用来表示沮丧,但我不确定它是如何与 Rc 一起使用的,因为它需要所有权(因此需要实例)。

解决方法

我最初期待答案是“绝对不会!”,如果您不知道具体类型,Any 没有帮助。但事实证明,您可以...注意事项,而且我不能 100% 确定它是完全安全的。

要从 Rc<T> 转到 Rc<U>,您可以使用逃生舱口 into_rawfrom_raw。前者的文档如下:

从原始指针构造一个 Rc。

原始指针必须先前已通过调用 Rc<U>::into_raw 返回,其中 U 必须与 T 具有相同的大小和对齐方式。如果 UT,则这是微不足道的。请注意,如果 U 不是 T 但具有相同的大小和对齐方式,这基本上类似于转换不同类型的引用。有关在这种情况下适用哪些限制的更多信息,请参阅 mem::transmute

from_raw 的用户必须确保 T 的特定值只删除一次。

这个函数是不安全的,因为使用不当可能会导致内存不安全,即使返回的 Rc<T> 从未被访问过。

考虑到这一点,由于我们只能访问 TraitA,因此它需要一个 as_b() 函数才能将自身作为 TraitB。目标是超级特质这一事实并没有真正的帮助。然后我们可以像这样写一个 crosscast 函数:

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))
    }
}

有了我们可以使用的这个函数,使用 .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();
    }
}

playground 上一起查看。

如果可能,我仍然建议不要这样做。例如,使用上述方法从 &Rc<dyn TraitA> 转到 Option<&dyn TraitB> 是完全安全的,没有所有限制。像这样的东西不会有限制和不安全:

for b in vec_a.iter().filter_map(|a| a.as_b()) {
   // ...
}