布局
首先,我们需要想出结构布局。一个 Vec 有三个部分:一个指向分配的指针,分配的大小,以及已经初始化的元素数量。
直观来说,这意味着我们只需要这样的设计:
pub struct Vec<T> {
ptr: *mut T,
cap: usize,
len: usize,
}
这确实可以编译成功。但是不幸的是,这是错误的。首先,编译器会给我们太严格的可变性(variance)。比如一个&Vec<&'static str>
不能用在预期&Vec<&'a str>
的地方。更重要的是,它将给 drop checker 提供不正确的所有权信息,因为它将保守地假设我们不拥有任何T
类型的值。参见所有权和生命周期一章中关于可变和 drop checker 的所有细节。
正如我们在所有权一章中看到的,当标准库拥有一个分配对象的原始指针时,它使用Unique<T>
来代替*mut T
。Unique 是不稳定的,所以如果可能的话,我们希望不要使用它。
简而言之,Unique 是一个原始指针的包装,并声明以下内容:
- 我们对
T
是协变的 - 我们可以拥有一个
T
类型的值(用于 drop checker) - 如果
T
是Send/Sync
,我们就是Send/Sync
。 - 我们的指针从不为空(所以
Option<Vec<T>>
是空指针优化的)
我们可以在稳定的 Rust 中实现上述所有的要求。为此,我们不使用Unique<T>
,而是使用NonNull<T>
,这是对原始指针的另一种包装,它为我们提供了上述的两个属性,即它在T
上是协变的,并且被声明为永不为空。通过添加一个PhantomData<T>
(用于丢弃检查),并在T
是Send/Sync
的情况下实现Send/Sync
,我们得到与使用Unique<T>
相同的结果:
use std::ptr::NonNull;
use std::marker::PhantomData;
pub struct Vec<T> {
ptr: NonNull<T>,
cap: usize,
len: usize,
_marker: PhantomData<T>,
}
unsafe impl<T: Send> Send for Vec<T> {}
unsafe impl<T: Sync> Sync for Vec<T> {}
# fn main() {}