Difference between PhantomData<T>, PhantomData<&T> and PhantomData<&mut T>

I am trying to implement Vector following the tutorial here Implementing Vec - The Rustonomicon and had a few questions with the Drain functionality.

  • Why do we need to specify PhantomData in the layout of Drain ? It seems to be because of lifetime but I am not quite sure why we need to specify lifetime itself. I understand the intent behind it which I believe is to say the values are valid for the lifetime of Drain. But with or without lifetime it compiles and can run the following test
    #[test]
    fn test_drain() {
        let mut p = Test { x : 1, y: 2 };
        let mut t = Vector::<Test>::new();
        t.push(p.clone());
        t.push(p.clone());
        t.push(p.clone());
        t.push(p.clone());

        {
            let mut drain_iter = t.drain();

            p = drain_iter.next().unwrap();
        }

        println!("Value of p.x = {}", p.x);
    }

In both cases, above test successfully compiles but fails with SIGSEGV error (Invalid memory reference). So is there any value in specifying lifetime/PhantomData in the layout of Drain ?

  • In case we do need the PhantomData what is the difference between PhantomData<T>; PhantomData<&T> and PhantomData<&mut T> ?

The lifetime prevents access to the Vec while the Drain is alive. If Drain did not have a lifetime at all, then it would not be considered as a borrow of the original Vec, and this can quickly lead to UB in safe code.

The difference between &T and &mut T is more about lifetime variance, since &mut T is invariant in T. It also affects auto-traits like Send and Sync.

2 Likes

The lifetime prevents access to the Vec while the Drain is alive. If Drain did not have a lifetime at all, then it would not be considered as a borrow of the original Vec , and this can quickly lead to UB in safe code.

That makes sense. Thank you. Now it makes even more sense why the PhantomData is on Vec<T>.

The difference between &T and &mut T is more about lifetime variance, since &mut T is invariant in T . It also affects auto-traits like Send and Sync .

Both of them are covariant over 'a but we need &mut Vec<T> in this case because of the constraint on lifetime of T right ?

Yes, although actually it's fine for Drain to be covariant in T as well, because it only ever moves items out of the collection, never writing items back in where such lifetime variance would cause a problem. It's not wrong to be invariant in T, just more limiting, and the standard library implementation of Drain is in fact covariant.

The auto traits are probably a bigger concern -- this is moving items, so Send for Drain should require T: Send, but leaving it auto based on PhantomData<&T> would have T: Sync. The standard library manually implements Send and Sync here.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.