Why `PhantomData<T>` is not equivalent to `T` in that case?

Hello, that code will not compile if there would be no PhantomData, but will compile if I use PhantomData. Can you please explain me, why?

trait Erase {}
impl<T> Erase for T {}

// Compiles like this:
struct Handle<'a>(std::marker::PhantomData<Box<dyn Erase + 'a>>);
// Not compiles like this:
// struct Handle<'a>(Box<dyn Erase + 'a>);

fn wrap<'a, F: FnOnce() + 'a>(_: F) -> Handle<'a> {
    todo!()
}

fn main() {
    let mut number = 3;

    let (tx, rx) = std::sync::mpsc::channel();

    tx.send(&mut number).unwrap();
    drop(tx);

    let _handle = wrap(move || drop(rx));

    core::hint::black_box(number);
}

You dont even need Erase to trigger it, these definitions are enough:

struct Handle<'a>(std::marker::PhantomData<std::sync::mpsc::Receiver<&'a mut i32>>);
struct Handle<'a>(std::sync::mpsc::Receiver<&'a mut i32>);

It feels like a Rust bug to be honest... Isn't it the whole purpose of PhantomData<T> to make type behave like it has T inside?

1 Like

The difference between these two cases is that Box and dyn Erase have destructors, but PhantomData does not regardless of its parameter. Values which have destructors are required[1] to have their lifetimes be valid when they are dropped (so that the Drop::drop() code will not be run on invalid references), but values without destructors can be dropped even after their lifetimes (and thus the validity any references they own) have ended.

In your specific case, Box<dyn Erase> has to run the destructor for the concrete type underlying dyn Erase.


  1. with a few #[may_dangle] exemptions, like Box<&T> and Vec<&T> â†Šī¸Ž

3 Likes

Oh, I see! I added Drop to the Handle and behavior became consistent again. Thank you!