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) -> Rc;

    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 Object 实际是两种类型,很多后续工作无法完成。

因此我们只能选择将 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) -> Rc {
        Rc::new(Self {
            inner: self.inner + other.as_any().downcast_ref::().inner
        })
    }
}

从而在不使用 raw pointer 的前提下实现了 trait object 到 struct 的转换。