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

Nov*_*vus 7 rust

如何PhantomData在Rust工作?在Nomicon中,它说如下:

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

对我而言,似乎暗示当我们PhantomData向结构中添加一个字段时,比如说是一个结构Vec.

pub struct Vec<T> {
    data: *mut T,
    length: usize,
    capacity: usize,
    phantom: PhantomData<T>,
}
Run Code Online (Sandbox Code Playgroud)

drop checker应该禁止以下代码序列:

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);
}
Run Code Online (Sandbox Code Playgroud)

由于的释放x,y以及z将发生的的释放Vec,我会期望从编译器的一些投诉.但是,如果您运行上面的代码,则没有警告或错误.

pnk*_*lix 10

PhantomData<T>Vec<T>(通过间接持有Unique<T>RawVec<T>)进行通信,以使得所述载体可以拥有的情况下,编译器T,因此,载体可运行的析构函数T当载体被丢弃.


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

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

  • 根据RFC 1238的规则,这通常意味着在实例中Vec<T>发生的实例和任何生命周期之间的关系T,要求所有生命周期内都T严格超过向量.

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

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

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

  • 在这种特定情况下,我们有一个Vec<T>(间接通过RawVec<T>/ Unique<T>)拥有一个类型值的集合,T以原始指针表示*const T.但是,编译器不会附加所有权语义*const T; 单独在结构中的那个字段S意味着和之间没有关系,因此在类型内的生命期关系和(至少从dropck的观点)方面强制执行约束.STST

  • 因此,如果Vec<T>*const T,递归下降到载体的结构将无法捕捉的矢量和的实例之间的所有制关系T的载体中包含.结合#[may_dangle]属性on T,将导致编译器接受不健全的代码(即T最终尝试访问已经解除分配的数据的析构函数).

  • 但是:Vec<T>完全包含*const T.还有一个PhantomData<T>,而且传达给编译器"哎,你尽管可以假定(由于#[may_dangle] T)的析构函数Vec将无法访问的数据T时,矢量被删除,它仍然可能是一些析构函数T 本身T作为向量的访问数据被删除."

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