Yep, I can imagine, in a fashion similar to PhantomPinned, having the following:
mod marker {
struct PhantomData<T> { // ...
struct PhantomPinned;
impl !UnPin for PhantomPinned {}
struct PhantomNotSync; // or type PhantomNotSync = PhantomData<Cell<()>>;
impl !Sync for PhantomNotSync {}
struct PhantomNotSend;
impl !Send for PhantomNotSend {}
// EDIT: &'static mut T requires T : 'static as @RalfJung pointed out
type PhantomInvariant<T> = PhantomData<fn(Box<T>) -> Box<T>>;
type PhantomInvariantLifetime<'a> = PhantomInvariant<&'a ()>;
}
However, I have found that expressing the invariants is easier when reasoning about the kind of access we have.; if, for instance, we had something along the lines of:
type UniqueIdMarker = core::marker::PhantomData<(/* redacted */)>;
struct UniqueId {
id: usize,
marker: UniqueIdMarker,
}
use ::std::cell::Cell;
type StaticCounter = Cell<usize>;
impl UniqueId {
fn new () -> Self
{
thread_local! {
static COUNTER: StaticCounter = Cell::new(0);
}
let current = COUNTER.with(Cell::get);
COUNTER.with(|slf| slf.set(
current
.checked_add(1)
.expect("UniqueId overflow")
));
UniqueId {
id: current,
marker: Default::default(),
}
}
}
What is the right Send / Sync property of our UniqueId struct? We could think a lot about this, or just realise that by using a static we are effectively holding a shared (zero-sized) reference to the counter. Thus:
type UniqueIdMarker = core::marker::PhantomData<&'static StaticCounter>;
// (Hence !Send and !Sync)
No need to think in terms of PhantomNotSend and/or PhantomNotSync, which could become overly strict if we changed StaticCounter to AtomicUsize, for instance.
This is why, for instance, PhantomContravariant should not exist (the only case where it is needed is for a callback argument, in which case PhantomData<fn(T)> (or fn(Box<T>) if T : ?Sized) expresses the intent much more clearly).