Trait Object 转换为 Struct
Rust Trick 之 Trait Object 转换为 Struct
<本文借鉴于 stackoverflow 上的这个问题的高赞回答。>
在C/C++里面,trait通常是以父类的形式出现的,父类转换为子类通常直接可以通过指针类型的转换就可以完成,当然C++也可以通过cast完成。
在rust里面当然也可以一切通过raw pointer来完成,但是我觉得在rust里面应该少用unsafe语句,因此也应该尽量少用raw pointer. 所以如何在不是用将tarit object转换为实现了该trait 的 struct 呢?
rust 官方提供了一个 trait 叫做 std::any::Any, 这个 trait 默认为所有不包含 non-static reference 的 struct 所实现。因此我们可以借助这个 trait 来实现类型转换。
写出我们的目标 trait 和 struct :
use std::rc::Rc;
use std::any::Any;
trait Object {
fn as_any(&self) -> &dyn Any;
fn add(&self, other: Rc<dyn Object>) -> Rc<dyn Object>;
fn print(&self);
}
struct Int {
inner: i32
}
如图 Object
用于表示所有类的父类,在 rust 里面表现为一个 trait,我们可以用 dyn Object
来表示任何实现了 Object
的类型。
然后我们定义一个整型类型,并为它实现 Object
trait, 但是问题来了,如果要实现 add
方法,我们必须要知道参数 other 的 inner 值,如果将取得值的方法定义在 Object
之内,由于每个类型的 inner 值不一样,因此必须写成 generic function 或者在 trait 内部定义一个 type Output
, 但是前者的话 Object
就不是 object safe 了,就无法定义 dyn Object
类型。如果是后者,则 Object<Output = i32>
和 Object<Output = String>
实际是两种类型,很多后续工作无法完成。
因此我们只能选择将 other 参数转换为实际的 Int
类型,因为我们确定 other 就是 Int
类型的实例。借助 Any
trait 的 downcast_ref
函数可以完成这个工作。
但是 downcast_ref
只有具体的 struct 类型以及 dyn Any
类型才能够调用,而 dyn Object
显然无法调用,因为不确定实现了 Object
trait 的类型内是否有 non-static reference, 因此在这个回答里面在 Object
内部定义了一个 as_any
函数将当前的实例引用转换为 &dyn Any
trait Object {
fn as_any(&self) -> &dyn Any;
}
并让 Int
实现这个函数:
impl Object for Int {
fn as_any(&self) -> &dyn Any {
self
}
}
之所以不能直接在 Object
内实现是因为 Any
要求任何类型转换为 Any
都要类型大小在编译期已知,因此在 Object
内部显然无法实现。
因此,我们就可以通过下面的方式进行类型转换从而实现 add
函数:
impl Object for Int {
fn add(&self, other: Rc<dyn Object>) -> Rc<dyn Object> {
Rc::new(Self {
inner: self.inner + other.as_any().downcast_ref::<Self>().inner
})
}
}
从而在不使用 raw pointer 的前提下实现了 trait object 到 struct 的转换。