2022年7月

在上一节中我们手工实现了虚函数表,完成了继承、重写以及向上转换的操作,接下来我们要实现向下转换,它的实现大概是下面的样子:

pub fn dynamic_cast<'a, B, D>(base: &'a B) -> Option<&'a D>
{
    if can_dynamic_cast_to::<B, D>(base)
    {
        Some(unsafe { reinterpret_cast(base) })
    }
    else
    {
        None
    }
}

这里我们使用了上一节实现的 reinterpret_cast 函数来进行类型的转换,实际上 reinterpret_cast 可以转换任何类型,而不管转换是否安全,因此才会被标记为 unsafe,上一节我们用它做向上转换,是因为我们可以确保转换是安全的,但在这里就要先行检测安全再进行转换。
为了实现 can_dynamic_cast_to 函数,我们需要一些额外的信息,也就是运行时类型识别,我们先简单实现一个:

pub struct TypeInfo
{
    base_class: Option<&'static TypeInfo>
}
impl TypeInfo
{
    fn is_same(&self, other: &TypeInfo) -> bool
    {
        self as *const TypeInfo == other as *const TypeInfo
    }
    fn is_base_of(&self, other: &TypeInfo) -> bool
    {
        let mut ret = self.is_same(other);
        if !ret
        {
            if let Some(other) = other.base_class
            {
                ret = self.is_base_of(other);
            }
        }
        ret
    }
}

这个类型信息简单了点,可以从当前类一直向上查找到基类为止,别看它小,但用于我们实现 can_dynamic_cast_to 足够了。
接下来要为每个类添加类型信息,如下:

pub struct BaseVTable
{
    _type_info_: &'static TypeInfo,
    ...
}
impl Base
{
    pub const TYPEINFO: TypeInfo = TypeInfo
    {
        base_class: None,
    };
    pub const VTABLE: BaseVTable = BaseVTable
    {
        _type_info_: &Self::TYPEINFO,
        ...
    };
}
pub struct Derive1VTable
{
    _type_info_: &'static TypeInfo,
    ...
}
impl Derive1
{
    pub const TYPEINFO: TypeInfo = TypeInfo
    {
        base_class: Some(&Base::TYPEINFO),
    };
    pub const VTABLE: Derive1VTable = Derive1VTable
    {
        _type_info_: &Self::TYPEINFO,
        ...
    };
}
pub struct Derive2VTable
{
    _type_info_: &'static TypeInfo,
    ...
}
impl Derive2
{
    pub const TYPEINFO: TypeInfo = TypeInfo
    {
        base_class: Some(&Derive1::TYPEINFO),
    };
    pub const VTABLE: Derive2VTable = Derive2VTable
    {
        _type_info_: &Self::TYPEINFO,
        ...
    };
}

现在我们可以实现 can_dynamic_cast_to 了,如下:

fn can_dynamic_cast_to<B, D>(base: &B) -> bool
{
    let typeinfo: &TypeInfo =
    {
        let p = base as *const B;
        let p = p as *const *const *const TypeInfo;
        unsafe { &***p }
    };
    &D::TYPEINFO.is_base_of(typeinfo)
}

根据我们实现的类的内存布局,无论是任何类,第一级指针,指向对象本身,这是毋庸置疑的;因为 vptr 是类的第一个成员,所以第二级指针都指向虚表;而虚表中第一个元素是指向类型信息的指针,所以第三级指针指向类型信息。因此我们将对象的引用转换为三级的类型信息指针,从而获取到对象的实际类型信息。
接下来我们通过 D::TYPEINFO 来获取要转换到的类型的类型信息,从而判断是否可以进行转换。但是这里存在两个问题:

  1. 如果用户传递给我们的 base 参数不是一个我们实现的类,甚至是一个 i32 或者 &str,怎么办?我们知道这样做是不合法的,却无法阻止这样的事情发生,甚至我们连安全性都不能保证。如下:

    if let Some(_) = dynamic_cast<i32, i64>(22) ...
    if let Some(_) = dynamic_cast<&str, Derive2>("abcd") ...
  2. D::TYPEINFO 无法通过编译,Rust 的模板要求在展开前进行语法检查,此刻 Rust 还不知道 D 的定义,这和 C++ 的模板不一样。

幸运的是,两个问题可以用一个方法解决,我们可以定义一个 trait TypeInfoTrait,并且我们的类都要求实现 TypeInfoTrait,如下:

pub unsafe trait TypeInfoTrait
{
    fn get_typeinfo() -> &'static TypeInfo;
}
unsafe impl TypeInfoTrait for Base
{
    fn get_typeinfo() -> &'static TypeInfo { &Self::TYPEINFO }
}
unsafe impl TypeInfoTrait for Derive1
{
    fn get_typeinfo() -> &'static TypeInfo { &Self::TYPEINFO }
}
unsafe impl TypeInfoTrait for Derive2
{
    fn get_typeinfo() -> &'static TypeInfo { &Self::TYPEINFO }
}

如此一来,我们可以要求模板参数 B、D 都实现 TypeInfoTrait,缩小了 dynamic_cast 方法的适用范围,在一定程度上保障了安全,但我们无法阻止用户自行实现 TypeInfoTrait,所以我们将它标记为 unsafe。

fn can_dynamic_cast_to<B, D>(base: &B) -> bool
where
    B: TypeInfoTrait,
    D: TypeInfoTrait,
{
    let typeinfo: &TypeInfo =
    {
        let p = base as *const B;
        let p = p as *const *const *const TypeInfo;
        unsafe { &***p }
    };
    &D::get_typeinfo().is_base_of(typeinfo)
}
pub fn dynamic_cast<'a, B, D>(base: &'a B) -> Option<&'a D>
where
    B: TypeInfoTrait,
    D: TypeInfoTrait,
...
pub fn dynamic_cast_mut<'a, B, D>(base: &'a mut B) -> Option<&'a mut D>
where
    B: TypeInfoTrait,
    D: TypeInfoTrait,
...

dynamic_cast 实现完成,我们来验证一下:

use crate::dynamic_cast;
fn func3(base: &super::Base) -> (i32, i32, i32)
{
    let z = if let Some(d1) = dynamic_cast::<super::Base, super::Derive1>(base)
    { d1.func3() } else { -1 };
    (base.func1(), base.func2(100), z)
}
#[test]
fn test_fn2()
{
    let b = super::Base::new(1, 2);
    assert_eq!((1, 102, -1), func3(&b));
    let d1 = super::Derive1::new(1, 2, 3);
    assert_eq!((3, 102, 3), func3(&d1));
    let d2 = super::Derive2::new(1, 2, 3);
    assert_eq!((3, 302, 203), func3(&d2));
}

自此,我们手工实现 C++ 类的功能已经验证完成,接下来我们要开始用宏来生成这些代码。

Rust 中的 trait 很神奇,有静态和动态两种用法,当静态使用时相当于 C++20 中引入的概念,动态使用时又相当于抽象基类,或者说是接口。
虽然 trait 可以派生自另一个 trait,结构体可以实现 trait,但是结构体不支持继承和派生,对于一个用惯了 C++ 的程序员来说,多少还是有些不适应,于是就想着用宏来模拟类的功能。
为简化实现,我不打算支持多重继承、私有继承、保护继承等不常用的特性,也不支持在同一个类中定义参数不同的同名函数,在 C++ 中叫做函数重载。
我想实现的效果如下,通过给结构体添加属性 #[class] 来提供类的功能,通过在函数前添加关键字 virtual 来声明虚方法,通过关键字 override 来重写基类的虚方法:

#[class]
pub struct Base
{
    x: i32,
    y: i32,
    fn new(x: i32, y: i32) -> Self { Base{ x, y } }
    virtual fn func1(&self) -> i32 { self.x }
    virtual fn func2(&self, i: i32) -> i32 { self.y + i }
}
#[class]
pub struct Derive1 : Base
{
    z: i32,
    fn new(x: i32, y: i32, z: i32) -> Self { Derive1 { Base::new(x, y), z} }
    override fn func1(&self) -> i32 { self.z }
    virtual fn func3(&self) -> i32 { self.z }
}
#[class]
pub struct Derive2 : Derive1
{
    override fn func2(&self, i: i32) -> i32 { Base::func2(self, i) + 200 }
    override fn func3(&self) -> i32 { Derive1::func3(self) + 200 }
}

为了实现虚函数及重载,需要我们自己来构建虚函数表,并在类中添加虚指针,Base 类可能会展开为如下的形式:

#[repr(C)]
pub struct BaseData
{
    x: i32,
    y: i32,
}
#[repr(C)]
pub struct BaseVTable
{
    func1: fn(this: &Base) -> i32,
    func2: fn(this: &Base, i: i32) -> i32,
}
#[repr(C)]
pub struct Base
{
    vptr: &'static BaseVTable,
    data: BaseData,
}
impl BaseData
{
    fn new(x: i32, y: i32) -> Self { BaseData{ x, y } }
}
impl Base
{
    const VTABLE: BaseVTable = BaseVTable
    {
        func1: Self::func1_impl,
        func2: Self::func2_impl,
    };
    pub fn new(x: i32, y: i32) -> Self
    {
        Base { vptr: &Self::VTABLE, data: BaseData::new(x, y) }
    }
    fn func1_impl(this: &Base) -> i32 { this.data.x }
    fn func2_impl(this: &Base, i: i32) -> i32 { this.data.y + i }
    pub fn func1(&self) -> i32 { (self.vptr.func1)(self) }
    pub fn func2(&self, i: i32) -> i32 { (self.vptr.func2)(self, i) }
}

一切都很完美,然后 Derive1 类应该展开为如下的形式:

#[repr(C)]
struct Derive1Data
{
    base: BaseData,
    z: i32,
}
#[repr(C)]
struct Derive1VTable
{
    func1: fn(this: &Base) -> i32,
    func2: fn(this: &Base, i: i32) -> i32,
    func3: fn(this: &Derive1) -> i32,
}
#[repr(C)]
pub struct Derive1
{
    vptr: &'static Derive1VTable,
    data: Derive1Data,
}
impl Derive1Data
{
    fn new(x: i32, y: i32, z: i32) -> Self { Derive1Data{ base: BaseData::new(x, y), z } }
}
impl Derive1
{
    const VTABLE: Derive1VTable = Derive1VTable
    {
        func1: Self::func1_impl,
        func2: Base::VTABLE.func2,
        func3: Self::func3_impl,
    };
    pub fn new(x: i32, y: i32, z: i32) -> Self
    {
        Derive1 { vptr: &Self::VTABLE, data: Derive1Data::new(x, y, z) }
    }
    fn func1_impl(this: &Base) -> i32
    {
        let this: &Self = unsafe { reinterpret_cast(this) };
        this.data.z
    }
    fn func3_impl(this: &Derive1) -> i32 { this.data.z }
    pub fn func3(&self) -> i32 { (self.vptr.func3)(self) }
}

我们看到对于 Derive1 的数据成员和虚函数表,我们采用了不同的方式,是因为我将数据成员为认定为私有的,派生类不可以直接访问,所以在派生类中访问基类的数据成员,需要多个 base. 我并不关心,也可以避免派生类和基类数据成员的命名冲突。
对于虚函数表,我不能接受 base.base.base.base.func1 = Self::func1_impl 这样的写法,不仅仅是丑的问题,最主要的是,我不知道需要多少个 base. 才能访问到 func1,为此我需要将基类的函数表展开到派生类,接下来是 Derive2 的展开:

type Derive2Data = Derive1Data;
type Derive2VTable = Derive1VTable;
#[repr(C)]
pub struct Derive2
{
    vptr: &'static Derive2VTable,
    data: Derive2Data,
}
impl Derive2
{
    const VTABLE: Derive2VTable = Derive2VTable
    {
        func1: Derive1::VTABLE.func1,
        func2: Self::func2_impl,
        func3: Self::func3_impl,
    };
    pub new(x: i32, y: i32, z: i32) -> Self
    {
        Derive2 { vptr: &Self::VTABLE, data: Derive2Data(x, y, z) }
    }
    fn func2_impl(this: &Base, i: i32) -> i32 { (Base::VTABLE.func2)(this, i) + 200 }
    fn func3_impl(this: &Derive1) -> i32 { (Derive1::VTABLE.func3)(this) + 200 }
}

由于 Derive2 没有数据成员和新增虚函数,所以数据和虚函数表的定义直接使用 Derive1 的就好了。
接下来到了验证阶段,我们来看下,我们实现的类及虚函数重写机制是否能正常工作:

#[cfg(test)]
mod tests
{
    fn func(base: &super::Base) -> (i32, i32)
    { (base.func1(), base.func2(100)) }
    fn func2(d1: &super::Derive1) -> (i32, i32, i32)
    { (d1.func1(), d1.func2(100), d1.func3() }
    #[test]
    fn test_fn()
    {
        let b = super::Base::new(1, 2);
        assert_eq!((1, 102), func(&b));
        let d1 = super::Derive1::new(1, 2, 3);
        assert_eq!((3, 102), func(&d1));
        assert_eq!((3, 102, 3), func2(&d1));
        let d2 = super::Derive2::new(1, 2, 3);
        assert_eq!((3, 302), func(&d2));
        assert_eq!((3, 302, 203), func2(&d2));
    }
}

很不幸,测试代码还不能工作。Rust 还不能理解三个类之间的关系,需要我们给类和基类之间建立联系,但在这之前,有两个基础函数,需要现行实现,如下:

#[inline(always)]
pub unsafe fn reinterpret_cast<'a, T, U>(t: &'a T) -> &'a U
{
    let p = t as *const T;
    &*(p as *const U)
}
#[inline(always)]
pub unsafe fn reinterpret_cast_mut<'a, T, U>(t: &'a mut T) -> &'a mut U
{
    let p = t as *mut T;
    &mut *(p as *mut U)
}

对于 C++ 程序员来说,可能会觉得眼熟。方法 reinterpret_cast 和 reinterpret_cast_mut,正如它们的名字一样,它们可以将任意一种类型转换为另外一种类型,而不做安全性检查,因此它们是不安全的。接下来我们要用这两个不安全的函数来做一些不寻常的事情。

use std::ops::Deref;
use std::ops::DerefMut;
impl Deref for Derive1
{
    type Target = Base;
    fn deref(&self) -> &Self::Target
    {
        unsafe { reinterpret_cast(self) }
    }
}
impl DerefMut for Derive1
{
    fn deref_mut(&mut self) -> &mut Self::Target
    {
        unsafe { reinterpret_cast_mut(self) }
    }
}
impl Deref for Derive2
{
    type Target = Derive1;
    fn deref(&self) -> &Self::Target
    {
        unsafe { reinterpret_cast(self) }
    }
}
impl DerefMut for Derive2
{
    fn deref_mut(&mut self) -> &mut Self::Target
    {
        unsafe { reinterpret_cast_mut(self) }
    }
}

我们为派生类实现 Deref 和 DerefMut 两个 triat,让派生类引用能够转换为基类的引用,而实现方法是 reinterpret_cast,可以说我们是在挑战 Rust 的安全性。我们可以这样做,是因为我们确信派生类和基类在基类大小的部分有着相同的内存布局。这也是我们给生成的结构体添加 #[repr(C)] 属性的原因。
现在,我们测试可以通过了,我们可以将派生类的引用转为基类的引用,也可以通过基类的引用来调用派生类的方法。
但是,如果我希望将基类引用转换为派生类引用该怎么办呢?在 C++ 中是通过 dynamic_cast 来进行向下转换,在下一节我们来实现它。