Rust Arc指针类型转换:dyn转换为具体类型

2023-10-18 10:49:42 浏览数 (1)

前言

在写代码的时候,经常通过dyn关键字 Arc指针来实现多态。但是,有时候会遇到这样一个需求:我们想获取具体类型的Arc指针。比如,结构体A实现了trait Base,想要把Arc<dyn Base>转换为Arc<A>.

为了实现这种转换,有开发者写了一个库,叫做downcast-rs,以支持上述要求。但是,这个库还要求了对象一定要被Box包裹,也就是,指针形式是Arc<Box<dyn Base>>转换为Arc<Box<A>>.由于DragonOS里面,文件系统的Inode指针原本就是Arc<dyn IndexNode>这种类型的,没有被Box包裹,因此不能直接使用downcast-rs这个库。

方法可行性验证

因此,在这里我想了一种方法(包含unsafe的)来实现这个需求。大概原理就是,判断Arc<dyn Base>的类型是否为A,如果是的话,就把Arc<dyn Base>转换为裸指针,再通过Arc::from_raw方法,得到Arc<A>。

我认为,既然Arc<dyn Base>是可以由Arc<A>直接转换而来,那么,只要我能确定Arc<dyn Base>的真实类型就是A,那应该就能转换回去。指向的目标的内存布局应该是一样的。

因此,我写了核心的代码:

代码语言:javascript复制
impl dyn Base {
    fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any> {
        return self;
    }
    fn downcast_arc<T: Base>(self: Arc<Self>) -> Option<Arc<T>> {
        
        let x = self.as_any_arc();

        if x.is::<T>() {
            
            // into_raw不会改变引用计数
            let p = Arc::into_raw(x);
            
            let new = unsafe { Arc::from_raw(p as *const T) };
            return Some(new);
        }
        println!("x.is not <{}>", std::any::type_name::<T>());
        return None;
    }
}

上述代码先把Arc<Self>转换为Arc<dyn Any>,然后,判断Self的类型是否为转换目标T。如果是的话,则调用Arc::into_raw和Arc::from_raw,完成Arc的类型转换。

下面是完整的测试程序,以及它的输出。可以看到,转换转换之后,能够正常调用具体类型结构体的成员函数。下面的测试程序中还跟踪了Arc指针的引用计数,可以发现,引用计数也是正常的。因此我“大胆猜测”,这里的转换代码没有问题。

代码语言:javascript复制
#![feature(trait_upcasting)]
use std::{any::Any, fmt::Debug, sync::Arc};

trait Base: Any   Send   Sync   Debug {
    fn get_name(&self) -> String;
}

impl dyn Base {
    fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any> {
        return self;
    }
    fn downcast_arc<T: Base>(self: Arc<Self>) -> Option<Arc<T>> {
        println!("count before: {}", Arc::strong_count(&self));
        let x = self.as_any_arc();
        let y = x.clone();

        if x.is::<T>() {
            println!("x.is::<{}>", std::any::type_name::<T>());
            println!("count in1: {}", Arc::strong_count(&y));
            // into_raw不会改变引用计数
            let p = Arc::into_raw(x);
            println!("count in2: {}", Arc::strong_count(&y));
            let new = unsafe { Arc::from_raw(p as *const T) };
            println!("count after: {}", Arc::strong_count(&new));
            return Some(new);
        }
        println!("x.is not <{}>", std::any::type_name::<T>());
        return None;
    }
}

#[derive(Debug)]
struct A {
    name: String,
}

impl Base for A {
    fn get_name(&self) -> String {
        self.name.clone()
    }
}

impl A {
    fn say_a(&self) {
        println!("say_a");
    }
}

#[derive(Debug)]
struct B {
    name: String,
}

impl Base for B {
    fn get_name(&self) -> String {
        self.name.clone()
    }
}

impl B {
    fn say_b(&self) {
        println!("say_a");
    }
}

fn main() {
    let a: Arc<A> = Arc::new(A {
        name: "A".to_string(),
    });
    let b: Arc<B> = Arc::new(B {
        name: "B".to_string(),
    });

    let x: Arc<dyn Base> = a as Arc<dyn Base>;
    let y: Arc<dyn Base> = b as Arc<dyn Base>;
    assert!(x.clone().downcast_arc::<A>().is_some());
    assert!(x.clone().downcast_arc::<B>().is_none());
    assert!(y.clone().downcast_arc::<A>().is_none());
    assert!(y.clone().downcast_arc::<B>().is_some());

    let a1: Arc<A> = x.downcast_arc::<A>().expect("x is not A");
    println!("a1: {:?}", a1);
    a1.say_a();

    let b1: Arc<B> = y.downcast_arc::<B>().expect("y is not B");
    println!("b1: {:?}", b1);
    b1.say_b();
}

输出:

代码语言:javascript复制
count before: 2
x.is::<downcast::A>
count in1: 3
count in2: 3
count after: 3
count before: 2
x.is not <downcast::B>
count before: 2
x.is not <downcast::A>
count before: 2
x.is::<downcast::B>
count in1: 3
count in2: 3
count after: 3
count before: 1
x.is::<downcast::A>
count in1: 2
count in2: 2
count after: 2
a1: A { name: "A" }
say_a
count before: 1
x.is::<downcast::B>
count in1: 2
count in2: 2
count after: 2
b1: B { name: "B" }
say_a

优雅地将它封装成trait

如果我们有多个trait都要实现以上功能,为每个trait单独写一套代码的话,就太不优雅了。因此,在这一步,我们将上述功能封装成一个叫做DowncastArc的trait:

代码语言:javascript复制
/// @brief 将Arc<dyn xxx>转换为Arc<具体类型>的trait
trait DowncastArc: Any   Send   Sync {
    /// 请在具体类型中实现这个函数,返回self
    fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any>;

    /// @brief 将Arc<dyn xxx>转换为Arc<具体类型>
    /// 
    /// 如果Arc<dyn xxx>是Arc<具体类型>,则返回Some(Arc<具体类型>),否则返回None
    /// 
    /// @param self Arc<dyn xxx>
    fn downcast_arc<T: Any   Send   Sync>(self: Arc<Self>) -> Option<Arc<T>> {
        let x: Arc<dyn Any> = self.as_any_arc();
        if x.is::<T>() {
            // into_raw不会改变引用计数
            let p = Arc::into_raw(x);
            let new = unsafe { Arc::from_raw(p as *const T) };
            return Some(new);
        }
        return None;
    }
}

这里我们用到了一个unstable的特性,因此需要在lib.rs中添加:

代码语言:javascript复制
#![feature(trait_upcasting)]

使用这个trait

接着,我们可以这样使用它,为Base这个trait实现DowncastArc:

代码语言:javascript复制
impl DowncastArc for dyn Base {
    fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any> {
        return self;
    }
}

请注意,这要求Base这个trait实现了Any Send Sync这三个trait.

测试代码与上文的main()函数的相同。

0 人点赞