60. Variance, PhantomData, and drop check
You need these when you write generic containers or unsafe code. You can
skip this chapter until you do.
PhantomData<T> tells the compiler that a type logically contains a
T when no field actually stores one. That affects three things: variance,
auto traits (Send/Sync), and drop checking.
use std::marker::PhantomData;
/// A typed ID: Id<User> and Id<Order> are different types with the same 8-byte representation.
struct Id<T> { raw: u64, _kind: PhantomData<fn() -> T> }
// `fn() -> T` instead of `T`: the Id does not OWN a T, so it is Send + Sync and Copy whatever T is.
// Derive would add `T: Clone` bounds we do not want, so implement by hand.
impl<T> Clone for Id<T> { fn clone(&self) -> Self { *self } }
impl<T> Copy for Id<T> {}
impl<T> PartialEq for Id<T> { fn eq(&self, o: &Self) -> bool { self.raw == o.raw } }
impl<T> Id<T> { fn new(raw: u64) -> Self { Id { raw, _kind: PhantomData } } }
struct User;
struct Order;
fn load_user(id: Id<User>) -> String { format!("user #{}", id.raw) }
fn main() {
let u: Id<User> = Id::new(7);
let o: Id<Order> = Id::new(7);
println!("{}", load_user(u));
// load_user(o); // error[E0308]: expected `Id<User>`, found `Id<Order>`
println!("{} bytes, same raw value: {}", size_of::<Id<User>>(), u.raw == o.raw);
}
Variance decides whether Foo<&'long T> can be used where
Foo<&'short T> is expected. &'a T is covariant (a longer-lived
reference can stand in for a shorter one). &'a mut T is invariant in
T. If it were not, you could write a short-lived reference into a slot
that expects a long-lived one, and that is a use-after-free.
PhantomData<T> makes your type covariant in T, PhantomData<fn(T)>
makes it contravariant, and PhantomData<*mut T> or PhantomData<Cell<T>>
makes it invariant. The Rustonomicon's chapter on subtyping has the full
table.
Drop check stops a Drop implementation from reading data that has
already been dropped. If your Drop impl for Container<T> might use a
T, PhantomData<T> tells the compiler that dropping a Container<T>
drops T values, so borrows inside T must still be alive at that point.