Description
According to the PhantomData
patterns table, there seems to be no difference between using e.g. *const T
and fn() -> T
. Both are covariant on T
and have no lifetime. But there actually is a difference, which is that PhantomData<*const T>
is not Send
or Sync
, whereas PhantomData<fn() -> T>
is Send
and Sync
.
This is something that users can figure out by thinking about how PhantomData
implements auto traits according to its T
parameter, but this isn't explicitly called out in the nomicon (or in the std::marker::PhantomData
docs) and so it's easy to forget. For example, I changed some code from PhantomData<T>
to PhantomData<fn() -> T>
specifically to avoid the drop check (my type doesn't own a T
, but instead it can produce T
s). It just so happens that this makes my type Send
and Sync
too, which it should have been in the first place but nobody realized that until well after the change.
This could be improved by updating the Nomicon to call out the fact that PhantomData
will implement auto traits according to its type parameter, and to give an example of this by including Send
/Sync
columns in the table that lists whether the given pattern is Send
/Sync
. This would look something like
Phantom type | 'a |
T |
Send |
Sync |
---|---|---|---|---|
PhantomData<T> |
- | covariant (with drop check) | T: Send |
T: Sync |
PhantomData<&'a T> |
covariant | covariant | T: Sync |
T: Sync |
PhantomData<&'a mut T> |
covariant | invariant | T: Send |
T: Sync |
PhantomData<*const T> |
- | covariant | - | - |
PhantomData<*mut T> |
- | invariant | - | - |
PhantomData<fn(T)> |
- | contravariant | Send |
Sync |
PhantomData<fn() -> T> |
- | covariant | Send |
Sync |
... |