Why is it useful to use PhantomData to inform the compiler that the structure is generic if I already implement Drop?

The Rustonomicon guide toPhantomData is part of what happens if the Vec-like structure has *const T, but not PhantomData<T>:

The droplet controller will generously determine that Vec<T>it has no type values T. This, in turn, will force him to conclude that there is no need to worry about Vecdeleting anyone Tin his destructor to determine the reliability of the drop test. This, in turn, will allow people to create bankruptcy using a destructor Vec.

What does it mean? If I implement Dropfor a structure and manually destroy everything Tin it, why should I be wondering if the compiler knows that my structure belongs to some Ts?

+4
source share
2 answers

PhantomData<T>inside Vec<T>(contained indirectly through Unique<T>inside RawVec<T>) tells the compiler that the vector can have instances T, and therefore the vector can trigger destructors for Twhen the vector is discarded.


Deep immersion: there are several factors:

  • We have one Vec<T>that has impl Drop(i.e. a destructor implementation).

  • RFC 1238 Vec<T> , T, , T .

  • Vec<T> ( Vec<T>) (. RFC 1238 RFC 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 S T , , S T ( , dropck).

  • , Vec<T> a *const T, T, , #[may_dangle] T , ( , T ).

  • : Vec<T> *const T. PhantomData<T>, ", (- #[may_dangle] T), Vec T, drop, , - 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();
}
+10

Caveat emptor - , . , Rust RFC. .


RFC 769 :

v ( ) 'a ( ); v D, (1.) D - Drop (2.) D &'a _ (3.) :

  • (A.) Drop impl D D 'a     , .. D<'a>, ,

  • (B.) Drop impl D      trait bound T T - ,       ,

'a v.

, , . PhantomData:

, , E D, :

E - PhantomData<T>, T.


, :

struct Noisy<'a>(&'a str);

impl<'a> Drop for Noisy<'a> {
    fn drop(&mut self) { println!("Dropping {}", self.0 )}
}

fn main() -> () {
    let (mut v, s) = (Vec::new(), "hi".to_string());
    let noisy = Noisy(&s);
    v.push(noisy);
}

, Drop-Check , Vec Noisy, . Vec , Drop ; .

:

Drop T , , , T s?

, , / Drop. Drop , , , .

, T , , , , .. .


; , RFC , , , .

. RFC 1238 Drop-Check, . :

, , dropck

PhantomData , . Twitter :

use std::marker::PhantomData;

#[derive(Debug)] struct MyGeneric<T> { x: Option<T> }
#[derive(Debug)] struct MyDropper<T> { x: Option<T> }
#[derive(Debug)] struct MyHiddenDropper<T> { x: *const T }
#[derive(Debug)] struct MyHonestHiddenDropper<T> { x: *const T, boo: PhantomData<T> }

impl<T> Drop for MyDropper<T> { fn drop(&mut self) { } }
impl<T> Drop for MyHiddenDropper<T> { fn drop(&mut self) { } }
impl<T> Drop for MyHonestHiddenDropper<T> { fn drop(&mut self) { } }

fn main() {
    // Does Compile! (magic annotation on destructor)
    {
        let (a, mut b) = (0, vec![]);
        b.push(&a);
    }

    // Does Compile! (no destructor)
    {
        let (a, mut b) = (0, MyGeneric { x: None });
        b.x = Some(&a);
    }

    // Doesn't Compile! (has destructor, no attribute)
    {
        let (a, mut b) = (0, MyDropper { x: None });
        b.x = Some(&a);
    }

    {
        let (a, mut b) = (0, MyHiddenDropper { x: 0 as *const _ });
        b.x = &&a;
    }

    {
        let (a, mut b) = (0, MyHonestHiddenDropper { x: 0 as *const _, boo: PhantomData });
        b.x = &&a;
    }
}

, RFC 1238 , lifetime type , .

, Vec , unsafe_destructor_blind_to_params, RFC.

+4

Source: https://habr.com/ru/post/1671948/


All Articles