布局

首先,我们需要确定结构体的布局。一个 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() {}