PhantomData在 rust 迹中是如何工作的?In the Nomicon it says the following:

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

对我来说,这似乎意味着当我们向一个 struct 中添加PhantomData字段时,比如Vec字段.

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

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);
}

由于xyz的释放将发生在Vec的释放上,我希望编译器会有一些抱怨.但是,如果运行上面的代码,则不会出现警告或错误.

推荐答案

Vec<T>中的PhantomData<T>(通过RawVec<T>中的Unique<T>间接持有)与编译器通信,该向量可能拥有T的实例,因此当向量被丢弃时,该向量可能会运行T的析构函数.


深度挖掘:我们这里有多种因素:

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

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

  • 然而,Vec<T>的析构函数通过使用特殊的不稳定属性(参见RFC 1238RFC 1327),特别地 Select 了这个语义for just that destructor(Vec<T>本身).这允许向量保存与向量本身具有相同生存期的引用.这被认为是合理的;毕竟,向量本身不会取消引用这些引用所指向的数据(它所做的只是删除值和取消分配支持数组),只要一个重要的警告成立.

  • 重要的警告是:虽然向量本身在销毁自身时不会取消对其包含值内指针的引用,但它会删除向量持有的值.如果T类型的值本身有析构函数,那么T的析构函数就会运行.如果这些析构函数访问它们的引用中保存的数据,那么我们将遇到一个问题if我们允许在这些引用中挂起指针.

  • 因此,更深入地探讨一下:我们确认给定 struct S的dropck有效性的方式是,我们首先仔细判断S本身是否有impl Drop for S(如果是,我们就S的类型参数对其执行规则).但即使在这一步之后,我们也会递归地判断S本身的 struct ,并根据dropck对其每个字段进行双重判断,确保所有内容都符合犹太规范.(请注意,即使类型参数S被标记为#[may_dangle],我们也会这样做.)

  • 在这个特定的例子中,我们有一个Vec<T>(通过RawVec<T>/Unique<T>间接地)拥有一个T类型的值集合,用一个原始指针*const T表示.然而,编译器没有将所有权语义附加到*const T;在 struct S中,仅该字段就意味着ST之间的no relationship,并且因此在类型ST内的生命周期 关系方面强制no constraint(至少从dropck的观点来看).

  • 因此,如果Vec<T>具有solely a *const T,则递归下降到向量 struct 中将无法捕获向量与向量中包含的T的实例之间的所有权关系.这与T上的#[may_dangle]属性相结合,将导致编译器接受不可靠的代码(即T的析构函数最终try 访问已解除分配的数据的情况).

  • 但是:Vec<T>并不意味着not只包含*const T.还有一个PhantomData<T>that向编译器传达"嘿,尽管你可以假设(由于#[may_dangle] T),当向量被丢弃时,Vec的析构函数不会访问T的数据,但是当向量被丢弃时,T itself的一些析构函数可能会访问T的数据."

最终效果:给定Vec<T>,如果T doesn't有一个析构函数,那么编译器为您提供了更大的灵活性(即,它允许一个向量保存引用数据的数据,引用的数据与向量本身存在的时间相同,即使这样的数据可能在向量生成之前被删除).但是,如果T does有一个析构函数(并且该析构函数没有以其他方式与编译器通信,表示它不会访问任何引用数据),那么编译器会更严格,要求任何引用的数据严格超过向量的生命周期 (从而确保当T的析构函数运行时,所有引用的数据仍然有效).

Rust相关问答推荐

将此字符串转换为由空格字符分隔的空格

在actix—web中使用Redirect或NamedFile响应

如何提高自定义迭代器的`extend`性能

当T不执行Copy时,如何返回Arc Mutex T后面的值?

替换可变引用中的字符串会泄漏内存吗?

如何在Rust中将选项<;选项<;字符串>;转换为选项<;选项&;str>;?

在铁 rust 中,如何一次只引用几件事中的一件?

Rust移动/复制涉及实际复制时进行检测

为什么不';t(&;mut-iter).take(n)取得iter的所有权?

Boxing 如何将数据从堆栈移动到堆?

`UnsafeCell` 在没有锁定的情况下跨线程共享 - 这可能会导致 UB,对吗?

std::vector::shrink_to_fit 如何在 Rust 中工作?

在线程中运行时,TCPListener(服务器)在 ip 列表中的服务器实例之前没有从客户端接受所有客户端的请求

无法把握借来的价值不够长寿,请解释

如何异步记忆选项中的 struct 字段

如何在 C++ 和 Rust 之间共享 pthread 同步原语?

Rustfmt 是否有明确类型的选项?

在 Rust 中退出进程

当特征函数依赖于为 Self 实现的通用标记特征时实现通用包装器

返回 &str 但不是 String 时,borrow 时间比预期长