In SmallVec v1, SmallVec<A> is invariant with respect to the generic parameter A. The reason is that its fields use the associated type projection A::Item, and the compiler treats that projection as invariant with respect to A.

We can see this directly in the v1 source code. The Array trait defines the associated type, and the implementation for arrays confirms that A::Item is T:

pub unsafe trait Array { type Item; fn size() -> usize; } // When A = [T; N] unsafe impl<T, const N: usize> Array for [T; N] { type Item = T; // ... } 

Then, inside the SmallVec struct itself, we have a PhantomData<A::Item>:

pub struct SmallVec<A: Array> { capacity: usize, data: SmallVecData<A>, _marker: PhantomData<A::Item>, } 

And the internal data storage also directly relies on the projection:

union SmallVecData<A: Array> { inline: core::mem::ManuallyDrop<MaybeUninit<A>>, heap: (NonNull<A::Item>, usize), } 

Because these projections are invariant, SmallVec<A> becomes invariant over A. And since A::Item = T when A = [T; N], this also makes SmallVec<[T; N]> invariant with respect to T, including lifetimes.

This often surprises users who expect it to behave like Vec (which is covariant over its T).

SmallVec v2 (currently alpha) changes the design in a way that avoids this, but until it stabilizes I wrote the covariant-vec crate as an alternative.

The same projection pattern also affects Cow<'a, B>: it is covariant over 'a, but invariant over B because of <B as ToOwned>::Owned. Others have already built the cow2 crate to address that case.

Have you run into other situations like this in your daily work—types where an associated-type projection accidentally introduces invariance? Maybe it would be useful to start a public catalog of such cases and discuss possible patterns/workarounds. I’d be happy to help get that going.

2 posts - 2 participants

Read full topic