在Rustonomicon的指南中PhantomData
,有一部分介绍了如果类似Vec
结构的*const T
字段具有字段但没有发生的情况PhantomData<T>
:
丢弃检查器将慷慨地确定它
Vec<T>
不拥有type的任何值T
。反过来,这将得出结论,无需担心在析构函数中Vec
丢弃任何T
用于确定丢弃检查健全性的。反过来,这将使人们能够使用Vec
的析构函数来产生不健全的感觉。
这是什么意思?如果我实现Drop
了一个结构并手动销毁了其中的所有T
s,那么为什么我应该关心编译器是否知道我的结构拥有一些T
s?
的PhantomData<T>
内Vec<T>
(通过间接持有Unique<T>
内RawVec<T>
的编译器连通),所述载体可以拥有的情况下T
,因此,载体可运行的析构函数T
当载体被丢弃。
深入探讨:我们综合考虑以下因素:
我们有一个Vec<T>
具有impl Drop
(即析构函数实现)的。
在RFC 1238的规则下,这通常意味着的实例Vec<T>
与其中发生的任何生存期之间存在关系,这是T
通过要求T
向量中的所有生存期都严格超过该向量而实现的。
但是,析构函数通过使用特殊的不稳定属性(请参阅RFC 1238和RFC 1327)专门针对该析构函数(本身)Vec<T>
选择退出此语义。这允许向量保存与向量本身具有相同生存期的引用。这被认为是声音;毕竟,只要有一个重要的警告说明,向量本身就不会取消引用此类引用所指向的数据(其所有工作就是丢弃值并取消分配后备数组)。Vec<T>
重要的警告:尽管向量本身在销毁自身时不会在其包含的值内取消对指针的引用,但会丢弃该向量所持有的值。如果这些类型的值T
本身具有析构函数,则将T
运行这些析构函数。如果这些析构函数访问其引用中保存的数据,那么如果我们允许在这些引用中悬挂指针,就会出现问题。
因此,我们将进行更深入的研究:在确定给定结构的dropck有效性的方式中S
,我们首先要仔细检查S
自身是否具有a impl Drop for S
(如果是,则S
对其类型参数强制执行规则)。但是,即使经过了这一步,我们仍然可以递归地进入其S
自身的结构,并根据dropck仔细检查其每个字段是否一切正常。(请注意,即使使用S
标记了类型参数,我们也会这样做#[may_dangle]
。)
在这种特定情况下,我们有一个Vec<T>
,它(通过RawVec<T>
/间接地Unique<T>
)拥有一个T
用原始指针表示的type值的集合*const T
。但是,编译器不将所有权语义附加到*const T
;。该字段在结构中本身并不S
意味着和之间没有关系,因此就类型和中的生存期关系而言(至少从dropck的角度来看),不施加任何约束。S
T
S
T
因此,如果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
运行的析构函数,所有引用的数据仍然有效)。
如果一个人想尝试通过具体的勘探明白这一点,你可以尝试比较如何在对待小容器类型的不同的编译器,在其使用的变化#[may_dangle]
和PhantomData
。
这是我整理的一些示例代码:
// Illustration of a case where PhantomData is providing necessary ownership
// info to rustc.
//
// MyBox2<T> uses just a `*const T` to hold the `T` it owns.
// MyBox3<T> has both a `*const T` AND a PhantomData<T>; the latter communicates
// its ownership relationship with `T`.
//
// Skim down to `fn f2()` to see the relevant case,
// and compare it to `fn f3()`. When you run the program,
// the output will include:
//
// drop PrintOnDrop(mb2b, PrintOnDrop("v2b", 13, INVALID), Valid)
//
// (However, in the absence of #[may_dangle], the compiler will constrain
// things in a manner that may indeed imply that PhantomData is unnecessary;
// pnkfelix is not 100% sure of this claim yet, though.)
#![feature(alloc, dropck_eyepatch, generic_param_attrs, heap_api)]
extern crate alloc;
use alloc::heap;
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::ptr;
#[derive(Copy, Clone, Debug)]
enum State { INVALID, Valid }
#[derive(Debug)]
struct PrintOnDrop<T: fmt::Debug>(&'static str, T, State);
impl<T: fmt::Debug> PrintOnDrop<T> {
fn new(name: &'static str, t: T) -> Self {
PrintOnDrop(name, t, State::Valid)
}
}
impl<T: fmt::Debug> Drop for PrintOnDrop<T> {
fn drop(&mut self) {
println!("drop PrintOnDrop({}, {:?}, {:?})",
self.0,
self.1,
self.2);
self.2 = State::INVALID;
}
}
struct MyBox1<T> {
v: Box<T>,
}
impl<T> MyBox1<T> {
fn new(t: T) -> Self {
MyBox1 { v: Box::new(t) }
}
}
struct MyBox2<T> {
v: *const T,
}
impl<T> MyBox2<T> {
fn new(t: T) -> Self {
unsafe {
let p = heap::allocate(mem::size_of::<T>(), mem::align_of::<T>());
let p = p as *mut T;
ptr::write(p, t);
MyBox2 { v: p }
}
}
}
unsafe impl<#[may_dangle] T> Drop for MyBox2<T> {
fn drop(&mut self) {
unsafe {
// We want this to be *legal*. This destructor is not
// allowed to call methods on `T` (since it may be in
// an invalid state), but it should be allowed to drop
// instances of `T` as it deconstructs itself.
//
// (Note however that the compiler has no knowledge
// that `MyBox2<T>` owns an instance of `T`.)
ptr::read(self.v);
heap::deallocate(self.v as *mut u8,
mem::size_of::<T>(),
mem::align_of::<T>());
}
}
}
struct MyBox3<T> {
v: *const T,
_pd: PhantomData<T>,
}
impl<T> MyBox3<T> {
fn new(t: T) -> Self {
unsafe {
let p = heap::allocate(mem::size_of::<T>(), mem::align_of::<T>());
let p = p as *mut T;
ptr::write(p, t);
MyBox3 { v: p, _pd: Default::default() }
}
}
}
unsafe impl<#[may_dangle] T> Drop for MyBox3<T> {
fn drop(&mut self) {
unsafe {
ptr::read(self.v);
heap::deallocate(self.v as *mut u8,
mem::size_of::<T>(),
mem::align_of::<T>());
}
}
}
fn f1() {
// `let (v, _mb1);` and `let (_mb1, v)` won't compile due to dropck
let v1; let _mb1;
v1 = PrintOnDrop::new("v1", 13);
_mb1 = MyBox1::new(PrintOnDrop::new("mb1", &v1));
}
fn f2() {
{
let (v2a, _mb2a); // Sound, but not distinguished from below by rustc!
v2a = PrintOnDrop::new("v2a", 13);
_mb2a = MyBox2::new(PrintOnDrop::new("mb2a", &v2a));
}
{
let (_mb2b, v2b); // Unsound!
v2b = PrintOnDrop::new("v2b", 13);
_mb2b = MyBox2::new(PrintOnDrop::new("mb2b", &v2b));
// namely, v2b dropped before _mb2b, but latter contains
// value that attempts to access v2b when being dropped.
}
}
fn f3() {
let v3; let _mb3; // `let (v, mb3);` won't compile due to dropck
v3 = PrintOnDrop::new("v3", 13);
_mb3 = MyBox3::new(PrintOnDrop::new("mb3", &v3));
}
fn main() {
f1(); f2(); f3();
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句