I know that PhantomData
is intended to consume a lifetime or type parameter in a data type definition that would otherwise go unused. I was recently looking over the definition of Rc
in the Rust std lib and noticed that it seems to employ PhantomData
, but it looks like T
is being used in the sibling field ptr
as an NonNull<RcBox<T>>
. The docs say that NonNull
is "*mut T but non-zero and covariant." and go on to further extend that definition with the statement:
Unlike
*mut T
,NonNull<T>
is covariant over T. If this is incorrect for your use case, you should include somePhantomData
in your type to provide invariance, such asPhantomData<Cell<T>>
orPhantomData<&'a mut T>
.
Hence, is it needed for variance or is it more because NonNull
is effectively a raw pointer and PhantomData
is needed to consume the elided lifetime as this answer seems to imply?
PhantomData
is used here to tell the drop checker that dropping Rc<T>
may cause a value of type T
to be dropped.
When we announce that we may drop a value of type T
, the drop checker ensures that any lifetimes in T
outlive the struct
itself. It is this check that prevents the following code from compiling. In this case, the generic argument to Rc
is PeekOnDrop<&'a u8>
, which has lifetime 'a
.
use std::{fmt, rc::Rc};
struct PeekOnDrop<T: fmt::Debug>(T);
impl<T: fmt::Debug> Drop for PeekOnDrop<T> {
fn drop(&mut self) {
println!("{:?}", self.0);
}
}
struct SelfReferential<'a> {
value: Box<u8>,
rc: Option<Rc<PeekOnDrop<&'a u8>>>,
}
fn main() {
let mut sr = SelfReferential {
rc: None,
value: Box::new(1),
};
sr.rc = Some(Rc::new(PeekOnDrop(&*sr.value)));
// `sr` would be dropped here, which could drop `value` before `rc`.
// The destructor of `PeekOnDrop` would then try to inspect the (dangling)
// reference, resulting in UB!
}
For a full explanation of the underlying logic here, see the nomicon, but note that without PeekOnDrop
, the previous example compiles just fine. This is because Rc<T>
declares in its Drop
impl that its generic parameter T
is #[may_dangle]
. In doing so, it promises that its Drop
impl does nothing with the value of T
that it owns except (maybe) drop it. Only when the drop checker recursively checks the Drop
impl of PeekOnDrop
and finds that it may access T
does an error occur.
For completeness, here's an example of an undefined program that lies by asserting that PeekOnDrop
's Drop
impl does not access T
using #[may_dangle]
. The same undefined behavior would be exhibited in the original example if Rc
did not use PhantomData
to declare that it may drop a value of T
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With