Dealing with unconstrained type parameters in impl blocks

Needing PhantomData does not necessarily mean you need PhantomData<T>.

The official documentation doesn't do a great job of explaining this, but the type argument for PhantomData should be chosen to have the same semantics as the type that holds it. This includes variance as well as drop behavior and auto traits such as Unpin. That means you should not use PhantomData<T> unless you're writing a type that semantically contains a T.

I'm not sure PhantomData is the right solution at all, but if it is, you need to use a type argument that conveys the actual relationship between WrapperSink<_, T> and T. It doesn't contain a T, but it can consume a T, at least, that seems to be the intended purpose. So try PhantomData<fn(T)>. Function pointers have no drop behavior, and they are Sync, Send, and Unpin, so you don't need to add a T: Unpin bound to make WrapperSink<_, T>: Unpin.

PhantomData<fn(T)> is contravariant in T, which is correct for a struct that does not store or produce a T. If you're writing any unsafe code, you might need PhantomData<fn(T) -> T> instead, which is invariant in T. Invariance is always the safest choice, so if you're unsure you might want to start with that and relax it to PhantomData<fn(T)> only when you encounter lifetime errors.

3 Likes