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 的转换。

posted @ 2022-01-28 00:14  kaleidopink  阅读(300)  评论(0编辑  收藏  举报