In regard to what you wrote here
I would say the corresponding type is:
struct Foo<T, const ASYNC: bool = false> {
/* … */
_unused: PhantomData<fn(fn(&T))>,
}
because you pass a closure, which takes a shared reference to T
as argument.
Maybe in this particular case, it might be better to simplify it though, depending on your implementation and/or other methods. Maybe if Foo<T, const ASYNC>
can/should actually be seen as "containing" a T
, then PhantomData<T>
might work just as fine.
(I assume PhantomData<fn(fn(&T))>
is covariant over T
, right? One fn
makes it contra-variant and the other makes it co-variant again, I would say.)
For example in mmtkvdb::Db
, I use this:
pub struct Db<K: ?Sized, V: ?Sized, C> {
key: PhantomData<fn(&K) -> &K>,
value: PhantomData<fn(&V) -> &V>,
constraint: C,
backend: ArcByAddr<DbBackend>,
}
Because I have methods that basically take a reference to K
and/or V
and return references to K
and/or V
.
Update: I just realized that the fn(&K) -> &K
only makes sense because I operate with &Db
(and not &mut Db
) when writing keys/values. Thus I need invariance, which is what PhantomData<fn(&K) -> &K>
does. However, if I would require a &mut Db
for writing keys/values, then covariance would be just fine. Compare: An Option<T>
is also covariant over T
, even if it has methods like Option::insert
, which take a T
as argument (but work on &mut self
). So the rule of trying to resemble the interface in the PhantomData
might be more tricky than I anticipated.