前言
在写代码的时候,经常通过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()函数的相同。