布局

首先,我们需要设计结构体的布局。一个 Vec 有三个部分:指向分配内存的指针、分配内存的大小以及已初始化元素的数量。

简单来说,这意味着我们想要这样的设计

pub struct Vec<T> {
    ptr: *mut T,
    cap: usize,
    len: usize,
}

实际上,这确实可以编译。不幸的是,它太严格了。编译器会给我们过于严格的变型。因此,&Vec<&'static str> 不能用于预期 &Vec<&'a str> 的地方。有关变型的所有详细信息,请参阅关于所有权和生命周期的章节

正如我们在所有权章节中看到的,标准库在拥有指向其所拥有分配内存的原始指针时使用 Unique<T> 代替 *mut T。不过,Unique 是不稳定的,所以我们希望尽可能不使用它。

概括地说,Unique 是一个围绕原始指针的包装器,它声明

  • 我们对 T 是协变的
  • 我们可能拥有类型为 T 的值(这对我们这里的示例无关紧要,但请参阅关于 PhantomData 的章节,了解为什么真正的 std::vec::Vec<T> 需要这样做)
  • 如果 T 是 Send/Sync,我们也是 Send/Sync
  • 我们的指针永远不会为空(因此 Option<Vec<T>> 是空指针优化的)

我们可以在稳定的 Rust 中实现上述所有要求。为此,我们将使用 NonNull<T>(另一个围绕原始指针的包装器)来代替 Unique<T>,它为我们提供了上述两个属性,即它对 T 是协变的,并且声明为永不为空。通过在 T 是 Send/Sync 时实现 Send/Sync,我们得到与使用 Unique<T> 相同的结果

use std::ptr::NonNull;

pub struct Vec<T> {
    ptr: NonNull<T>,
    cap: usize,
    len: usize,
}

unsafe impl<T: Send> Send for Vec<T> {}
unsafe impl<T: Sync> Sync for Vec<T> {}
fn main() {}