如果我已经实现了Drop,为什么使用PhantomData通知编译器结构拥有泛型有用吗?

代码三明治

Rustonomicon的指南中PhantomData,有一部分介绍了如果类似Vec结构的*const T字段具有字段但没有发生的情况PhantomData<T>

丢弃检查器将慷慨地确定它Vec<T>不拥有type的任何值T反过来,这将得出结论,无需担心在析构函数中Vec丢弃任何T用于确定丢弃检查健全性的。反过来,这将使人们能够使用Vec的析构函数来产生不健全的感觉

这是什么意思?如果我实现Drop了一个结构并手动销毁了其中的所有Ts,那么为什么我应该关心编译器是否知道我的结构拥有一些Ts?

彭费利克斯

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自身是否具有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的角度来看),不施加任何约束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运行的析构函数,所有引用的数据仍然有效)。


如果一个人想尝试通过具体的勘探明白这一点,你可以尝试比较如何在对待小容器类型的不同的编译器,在其使用的变化#[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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么C#编译器在catch中授权“ throw ex”,并且在某些情况下“ throw ex”有用吗?

为什么编译器声称泛型即使应有也不实现`Display`?

拥有专用的锁对象有用吗?

处理泛型时编译器没有用的期望?

如果我使用-crf 0,FFmpeg预设会有用吗?

为什么运算符<对于Java泛型有编译器错误?

编译器告诉我当我已经拥有了时执行compareTo()方法

自我分配有用吗?

ACPI在服务器上有用吗?

为什么编译器在具有双界泛型类型时不将 T(interface) 推断为实现 T 的类?

<< pseudo:before >> </ <pseudo:before >>到底是什么,对我有用吗?

使用Virtualbox进行游戏,对您有用吗?

Angular 对 JWT 使用 Booth cookie 或拦截器有用吗?

GLSL memoryBarrierShared()有用吗?

.cpp文件有用吗?

为什么这种带有泛型的Kotlin代码会产生Type Mismatch编译器错误?

为什么编译器不推断泛型

AutoLayout对处理iOS屏幕大小的碎片有用吗?为什么Android中没有等效的工具?

任何人都可以清楚地说明为什么Google Guice有用吗?

有人可以向我解释使用MapMaker或WeakHashMaps何时有用吗?

为什么这个java接口定义使用编译器接受的泛型?

为什么编译器可以与未关闭的泛型一起使用?

是否已经有用于Android的StopWatch类,为什么我的实现无法正常工作?

Java源文件-编译后编码仍然有用吗?

引用仅在constexpr /编译时上下文中有用吗?

BlockingQueue对实现生产者/消费者系统有用吗?

为什么编译器已经告诉我要考虑使用“ let”绑定?

有 32 个浮点标量寄存器有用吗?

C#-Assert()方法有什么作用?它仍然有用吗?