2026年6月

在 Rust 中,自定义类型要么是复制语义,要么是移动语义,而我更喜欢移动语义。然而移动语义的类型也可能需要复制的操作,这一点 Rust 早就给我们想好了。这就是 Clone Trait,深度复制,实现 Clone Trait 很简单,大部分情况只需要给类型定义添加 #[derive(Clone)] 属性即可:

#[class]
#[derive(Clone)]
pub struct Base
{
    x: i32,
    y: i32,
    ...
}

不过呢,有时我们希望自行实现 Clone Trait,我们可以这样做:

#[class]
pub struct Base
{
    x: i32,
    y: i32,
    fn clone(&self) -> Self { Self { x: self.x, y: self.y } }
    fn clone_from(&mut self, other: &Self) { self.x = other.x; self.y = other.y; }
    ...
}

class 宏识别到类有方法名为 clone 时,会为类实现 Clone Trait,出于优化的目的,你也可以选择同时实现 clone_from 方法。之所以要绕这么大的圈子,还是因为虚表指针的赋值操作不能由用户来操作,必须交给 class 宏来进行,也就是说 class 宏会分析 clone 及 clone_from 方法,添加虚表指针的赋值操作。有了之前在构造函数中初始化虚表指针的经验,为 clone 方法复制虚表指针也不难:

pub struct CloneVPTR<'a>
{
    class_name: &'a str,
    blocked: bool,
}
impl<'a> VisitMut for CloneVPTR<'a>
{
    fn visit_expr_mut(&mut self, expr: &mut Expr)
    {
        if !self.blocked
        {
            if let Expr::Struct(expr_stru) = expr
            {
                if let Some(seg) = expr_stru.path.segments.last()
                {
                    let name = seg.ident.to_string()
                    if self.class_name == name || "Self" == name
                    {
                        expr_stru.fields.push(parse_quote!(vptr: self.vptr));
                    }
                }
            }
        }
        syn::visit_mut::visit_expr_mut(self, expr);
        self.blocked = false;
    }
}

clone 方法不需要处理派生类,因为在基类中就已经完成了对虚表指针的赋值,比构造函数要简单的多。
我们来验证一下刚刚实现的深度复制方法。

fn get_data(base: &Base) -> (i32, i32, i32)
{
    let z = if let Some(d1) = dynamic_cast::<Base, Derive1>(base)
    { d1.z } else { -1 };
    (base.x, base.y, z)
}
fn print_address_and_data(b: &Base)
{
    println!("the address = {:p}, vptr = {:p}, data = {:?}.", b, b.vptr, get_data(b));
}
macro_rules! clone_and_add
{
    ($expr:expr, $x:expr, $y:expr) => { { let mut c = $expr.clone(); c.x += $x; c.y += $y; c } };
}

函数 get_data 用来获得对象的数据,函数 print_address_and_data 用来打印对象的地址和数据,而宏 clone_and_add 用来创建副本并修改数据,以便和原始对象加以区别。有了辅助函数和宏,我们可以写测试方法了:

fn test_clone()
{
    let b = CBase::new(1, 2);
    let bc = clone_and_add!(b, 100, 100);
    let d1 = CDerive1::new(3, 4, 5);
    let d1c = clone_and_add!(d1, 200, 200);
    let d2 = CDerive2::new(6, 7, 8);
    let d2c = clone_and_add!(d2, 300, 300);
    print_address_and_data(&b);
    print_address_and_data(&bc);
    print_address_and_data(&d1);
    print_address_and_data(&d1c);
    print_address_and_data(&d2);
    print_address_and_data(&d2c);
}

运行结果如下:

the address = 0x7f1b9727a4d0, vptr = 0x55a1c3339b58, data = (1, 2, -1).
the address = 0x7f1b9727a4e0, vptr = 0x55a1c3339b58, data = (101, 102, -1).
the address = 0x7f1b9727a508, vptr = 0x55a1c3339b98, data = (3, 4, 5).
the address = 0x7f1b9727a520, vptr = 0x55a1c3339b98, data = (203, 204, 5).
the address = 0x7f1b9727a558, vptr = 0x55a1c3339be0, data = (6, 7, 8).
the address = 0x7f1b9727a570, vptr = 0x55a1c3339be0, data = (306, 307, 8).
Derive2::drop.
Derive1::drop.
Base::drop.
Derive2::drop.
Derive1::drop.
Base::drop.
Derive1::drop.
Base::drop.
Derive1::drop.
Base::drop.
Base::drop.
Base::drop.

我们新建了三个对象,深度复制了三个对象,共六个对象,地址不同,且对象大小正确,虚表指针正确,数据正确,析构函数的调用顺序和对象创建的顺序相反。符合预期,测试成功。

智能指针的深度复制方法的实现

接下来要为智能指针支持深度复制操作,我想你们已经猜到了,要走虚机制,如虚析构函数一样。但这样一来虚表中又多了一个用户看不见的方法,而且如果以后还需要实现什么虚调用的方法,又要在虚表中增加方法,这不利于虚表的稳定。因此,我将虚 drop 和 虚 clone 方法都移到了类型信息中,不再占用虚表的槽位,如下:

pub struct TypeInfo
{
    base_class: Option<&'static TypeInfo>,
    layout: std::alloc::Layout,
    drop: unsafe fn(*mut ()),
    clone: Option<unsafe fn(*const ()) -> *mut ()>,
    clone_from: Option<unsafe fn(*mut (), *const ())>,
}

如果以后要增加虚调用,直接加在类型信息里,不会破坏二进制的兼容。其中 drop、clone 和 clone_from 方法都抹去了具体类型,而以空元组指针代替,相当于 C++ 的 void*,这是因为智能指针调用这些方法时,不关心也不知道具体的类型,只需要知道参数是一个指针就够了。当然你也可以选择将 TypeInfo 类型定义为模板,这样每个类的 TypeInfo 都是不同的类型,但是如何定义 base_class 的类型会比较有点麻烦。
深度复制和析构函数有两点不同:

  1. 无论类型是否实现 Drop Trait,都可以被析构,而如果类型不实现 Clone Trait,就没有 clone 和 clone_from 方法可供调用,因此 drop 是必选项,而 clone 和 clone_from 是可选项;
  2. Rust 内置了 drop_in_place 方法可以供 drop 方法使用,而 clone 和 clone_from 方法要我们自己实现。

我们知道智能指针的数据是在栈上,因此深度复制后的数据也应该是在栈上,这里涉及到内存分配,内存分配我们不陌生,之前为智能指针实现构造函数时的代码正好拿出来用,如下:

unsafe fn alloc_value<T: TypeInfoTrait>(value: T) -> *mut T
{
    let layout = T::get_typeinfo().layout();
    assert_ne!(std::mem::size_of::<T>(), 0, "not support.");
    let ptr = unsafe { std::alloc::alloc(*layout) };
    if ptr.is_null()
    {
        std::alloc::handle_alloc_error(*layout);
    }

    let ptr = ptr as *mut T;
    unsafe { std::ptr::write(ptr, value); }
    ptr
}

有了函数 alloc_value,我们可以支持栈到栈的深度复制:

unsafe fn clone_ptr<T: TypeInfoTrait + Clone>(value: *const T) -> *mut T
{
    alloc_value((*value).clone())
}

方法 clone_from 是在现有的对象上直接构造,不需要重新分配内存:

unsafe fn clone_from_ptr<T: TypeInfoTrait + Clone>(value: *mut T, source: *const T)
{
    (*value).clone_from(&*source)
}

无论是 alloc_value 还是 clone_ptr 都涉及到内存分配和解引用原生指针,为防止误用,我标记他们为不安全的。
支持深度复制的类,类型信息的 clone 方法指针,都会指向 clone_ptr 方法。我相信你们也看到了,clone_ptr 方法和 TypeInfo 类型的 clone 成员类型不一致,解决方法是强制类型转换, clone_from 方法同理:

const unsafe fn convert_clone_fn<T>(f: unsafe fn(*const T) -> *mut T)
     -> unsafe fn (*const ()) -> *mut ()
where T: TypeInfoTrait + Clone,
{
    let p = &f as *const unsafe fn(*const T) -> *mut T;
    let p = p as *const unsafe fn (*const()) -> *mut();
    *p
}
const unsafe fn convert_clone_from_fn<T>(f: unsafe fn(*mut T, *const T))
     -> unsafe fn (*mut (), *const ())
{ ... }
pub const fn new<T>(base_class: Option<&'static TypeInfo>) -> Self
where T: TypeInfoTrait + Clone,
{
    let layout = std::alloc::Layout::new::<T>();
    let drop = unsafe { Self::convert_drop_fn(std::ptr::drop_in_place::<T>) };
    let clone = unsafe { Self::convert_clone_fn(clone_ptr::<T>) };
    let clone_from = unsafe { Self::convert_clone_from_fn(clone_from_ptr::<T>) };
    Self { base_class, layout, drop, clone: Some(clone), clone_from: Some(clone_from) }
}

下面为智能指针实现深度复制方法,如下:

impl<T: TypeInfoTrait + Clone> Clone for DynBox<T>
{
    fn clone(&self) -> Self
    {
        let ptr = self.ptr.as_ptr();
        unsafe
        {
            let ptr_typeinfo = ptr as *const *const *const TypeInfo;
            let new_ptr = ((&***ptr_typeinfo).clone.unwrap())(ptr as *const());
            let ptr = std::ptr::NonNull::new_unchecked(new_ptr as *mut T);
            Self { ptr, _marker: std::marker::PhantomData }
        }
    }
}

clone 方法的实现很简单,找到类型信息,然后调用 clone 方法,得到新的内存,构造新的智能指针,工作完成。
clone_from 方法的实现稍显复杂:

fn clone_from(&mut self, source: &Self)
{
    let mut self_ptr = self.ptr.as_ptr();
    let source_ptr = source.ptr.as_ptr();
    unsafe
    {
        let self_typeinfo = &***(self_ptr as *const *const *const TypeInfo);
        let source_typeinfo = &***(source_ptr as *const *const *const TypeInfo);
        (self_typeinfo.drop)(self_ptr as *mut());
        if self_typeinfo.layout() != source_typeinfo.layout()
        {
            std::alloc::dealloc(self_ptr as *mut u8, *self_typeinfo.layout());
            let ptr = std::alloc::alloc(*source_typeinfo.layout());
            if ptr.is_null()
            {
                std::alloc::handle_alloc_error(*source_typeinfo.layout());
            }
            self_ptr = ptr as *mut T;
        }
        (source_typeinfo.clone_from.unwrap())(self_ptr as *mut(), source_ptr as *const());
        self.ptr = std::ptr::NonNull::new_unchecked(self_ptr as *mut T);
    }
}

我们首先拿到两个指针的类型信息,将 self 指针析构,然后判断两个类型的大小及对齐是否一致,如果一致的话,就要重新分配内存,最后调用 source 类型的 clone_from 方法来复制对象。
对于多态的对象,self 和 source 都有相同的基类,也就是 Dynbox 的模板参数 T,但可能 self 和 source 所指向的实际类型并不相同,因为我们要用 source 对象来重新初始化 self 对象,所以我们要用 source 类型的 clone_from 方法。
clone_from 通常我们不需要实现,作为一种优化,clone_from 在某些情况下省去了重新分配内存的开销,同时如果类型本身提供了优化的 clone_from 方法,也能够被调用到。

所有类都会实现 Clone Trait

这里还有一个问题,就是如果 TypeInfo 的 clone 和 clone_from 成员为 None,就会引发运行时崩溃,我们没有做更进一步的处理。这样的情况怎么会发生呢?只有类型 T 实现了 Clone Trait,我们才会为 DynBox 实现 Clone Trait,所以 T 的类型信息的 clone 成员一定不为空。我们仅仅能够约束类型 T,而无法约束 T 的子类。如果 T 的子类 U 没有实现 Clone Trait,而 DynBox 指向 U 的对象时,调用 DynBox::clone 方法就会崩溃。
因此,这里有一个隐性的要求,如果基类实现了 Clone Trait,那么子类必须也实现 Clone Trait。否则智能指针 clone 方法不能保能正常工作。为简单起见,我为所有的类都实现了 Clone Trait,当然会有开销,待以后再去优化。
代码完成,我们来验证一下:

fn test_clone_ptr()
{
    let mut v = Vec::<DynBox<Base>>::new();
    v.push(DynBox::new(Base::new(1, 2)));
    v.push(DynBox::new(Derive1::new(3, 4, 5)));
    v.push(DynBox::new(Derive2::new(6, 7, 8)));
    v.push(clone_and_add!(v[0], 100, 100));
    v.push(clone_and_add!(v[1], 200, 200));
    v.push(clone_and_add!(v[2], 300, 300));
    for b in &v
    {
        print_address_and_data(&b);
    }
}

首先,我们创建三个智能指针,分别指向 Base、Derive1 和 Derive2 三个类的对象,创建副本,并输出对象地址、虚表指针和数据,结果如下:

the address = 0x7f0820000ce0, vptr = 0x5567e3435b58, data = (1, 2, -1).
the address = 0x7f0820000d00, vptr = 0x5567e3435b98, data = (3, 4, 5).
the address = 0x7f0820000d20, vptr = 0x5567e3435be0, data = (6, 7, 8).
the address = 0x7f0820000d40, vptr = 0x5567e3435b58, data = (101, 102, -1).
the address = 0x7f0820000d60, vptr = 0x5567e3435b98, data = (203, 204, 5).
the address = 0x7f0820000dd0, vptr = 0x5567e3435be0, data = (306, 307, 8).
Base::drop.
Derive1::drop.
Base::drop.
Derive2::drop.
Derive1::drop.
Base::drop.
Base::drop.
Derive1::drop.
Base::drop.
Derive2::drop.
Derive1::drop.
Base::drop.

我们看到栈上分配的内存地址不够连贯,也许和 Rust 的内存分配策略有关,但对于小对象,内存浪费的现象会比较严重,也可以自行实现内存池来优化小对象的内存分配。当然这不是我们现在要讨论的。
对象析构的顺序是按对象在 Vec 中存储的顺序来的。验证成功。
现在,类和智能指针都支持了深度复制。实用性已经进一步提升了。

安全隐患

但这里还有一个问题没有解决,如果用引用调用 clone 方法,则不会触发虚机制,如下:

fn test_clone(b: &Base)
{
    let b2 = b.clone();
    b.func1(...);    // b 可能是 Base 类对象也可能是 Base 的派生类对象
    b2.func1(...);   // b2 是 Base 类对象。因此两个 func1 方法可能有不同的行为。
}
let b = DynBox::<Base>::new(Derive::new(...));
test_clone(&b);

在函数 test_clone 中,用户本来希望用对象的副本来做一些事情,但是副本和原始对象的类型不同,因而会有不同的行为。从而导致不可察觉的错误发生。

到这里,类功能的实现已经接近尾声了。但还有一个很重要的语法我们没有支持,下一节我们来处理这件事。