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