布局

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

直观来说,这意味着我们只需要这样的设计:

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

这确实可以编译成功。但是不幸的是,这有些过于严格了。编译器会给我们太严格的可变性(variance)。比如一个&Vec<&'static str>不能用在预期&Vec<&'a str>的地方。参见所有权和生命周期一章中关于可变和 drop checker 的所有细节。

正如我们在所有权一章中看到的,当标准库拥有一个分配对象的原始指针时,它使用Unique<T>来代替*mut T。Unique 是不稳定的,所以如果可能的话,我们希望不要使用它。

简而言之,Unique 是一个原始指针的包装,并声明以下内容:

  • 我们对T是协变的
  • 我们可以拥有一个T类型的值(这和我们在这的例子无关,但是可以参考PhantonData那章来看看为什么真正的std::vec::Vec<T>需要这个)
  • 如果TSend/Sync,我们就是Send/Sync
  • 我们的指针从不为空(所以Option<Vec<T>>是空指针优化的)

我们可以在稳定的 Rust 中实现上述所有的要求。为此,我们不使用Unique<T>,而是使用NonNull<T>,这是对原始指针的另一种包装,它为我们提供了上述的两个属性,即它在T上是协变的,并且被声明为永不为空。通过在TSend/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() {}