什么是`PhantomData`实际上在`Vec`的实现?

时间:2017-01-08 14:01:47

标签: rust

PhantomData如何在Rust中运行? In the Nomicon it says the following

  

为了告诉dropck我们自己拥有类型T的值,因此当我们删除时可能会删除一些T,我们必须添加一个额外的PhantomData来说明这一点。

对我而言,似乎暗示当我们向结构中添加PhantomData字段时,例如Vec

pub struct Vec<T> {
    data: *mut T,
    length: usize,
    capacity: usize,
    phantom: PhantomData<T>,
}

drop查看器应该禁止以下代码序列:

fn main() -> () {
    let mut vector = Vec::new();

    let x = Box::new(1 as i32);
    let y = Box::new(2 as i32);
    let z = Box::new(3 as i32);

    vector.push(x);
    vector.push(y);
    vector.push(z);
}

由于释放xyz会在<{>> 释放Vec之前发生,我会指望一些投诉来自编译器。但是,如果您运行上面的代码,则没有警告或错误。

1 个答案:

答案 0 :(得分:7)

PhantomData<T>内的Vec<T>(通过Unique<T>内的RawVec<T>间接持有)与编译器通信,该向量可能拥有T的实例,并且因此,当向量被删除时,向量可以运行T的析构函数。

深度探讨:我们在这里有多种因素:

  • 我们有一个Vec<T>,其impl Drop(即析构函数实现)。

  • 根据RFC 1238的规则,这通常意味着Vec<T>的实例与T内发生的任何生命期之间的关系,要求T内的所有生命周期1}}严格超过矢量。

  • 但是,Vec<T>的析构函数通过使用特殊的不稳定属性专门选择了这个语义,只有那个析构函数Vec<T>本身)(见RFC 1238RFC 1327)。这允许向量保存具有与向量本身相同的生命周期的引用。这被认为是合理的;毕竟,只要一个重要的警告成立,向量本身就不会取消引用这些引用所指向的数据(它所做的只是删除值并释放后备数组)。

  • 重要的警告:虽然向量本身在破坏自身时不会在其包含的值中取消引用指针,但删除向量所持有的值。如果类型T的值本身具有析构函数,那么T的析构函数就会运行。如果这些析构函数访问其引用中的数据,那么我们就会遇到问题如果我们允许在这些引用中悬挂指针。

  • 因此,深入探讨:我们确认给定结构S的丢包有效性的方式,我们首先仔细检查S本身是否有impl Drop for S(如果是这样,我们就S的类型参数强制执行规则。但即使在那一步之后,我们也递归地下降到 S本身的结构中,并根据dropck仔细检查每个字段是否一切都是犹太的。 (请注意,即使S的类型参数标有#[may_dangle],我们也会执行此操作。)

  • 在这种特定情况下,我们有Vec<T>(间接通过RawVec<T> / Unique<T>)拥有T类型值的集合,代表原始指针*const T。但是,编译器不会将所有权语义附加到*const T;结构S中单独的字段暗示ST之间的无关系,因此强制执行无约束类型ST中的生命周期关系(至少从dropck的角度来看)。

  • 因此,如果Vec<T>只有 一个*const T,那么递归下降到向量结构将无法捕获向量之间的所有权关系以及向量中包含的T个实例。这与#[may_dangle]上的T属性相结合,将导致编译器接受不健全的代码(即T的析构函数最终尝试访问已经解除分配的数据的情况)。

  • 但是:Vec<T> 只包含*const T。还有一个PhantomData<T> 传达给编译器&#34;嘿,即使您可以假设(由于#[may_dangle] T)析构函数为{{ 1}}在删除向量时无法访问Vec的数据,仍然可能是T 本身的某些析构函数当向量被删除时,将访问T的数据。&#34;

结束效果:给定T,如果Vec<T> 没有具有析构函数,那么编译器会为您提供更大的灵活性(即,它允许向量来保存数据,其中引用的数据与向量本身的生存时间相同,即使这些数据可能在向量之前被拆除。但是如果T 确实有一个析构函数(并且析构函数没有与编译器进行通信而无法访问任何引用的数据),则编译器更严格,需要任何引用的数据都要严格超过向量(从而确保当T的析构函数运行时,所有引用的数据仍然有效)。