Box implements Unpinunconditionally. For some people, it is an historical mistake, others are more nuanced, but the implementation has been stabilized and is here to stay.
However, Vec, which is the resizable twin of Box, doesn't have this unconditional implementation but the auto-trait one. It doesn't seem consistent to me, with unintuitive side-effect; for example switching Box<[T]> to Vec<T> in a struct can change the Unpin implementation.
A somewhat tricky general fact of auto traits is that the same argument applies to adding unconditional Unpin to Vec now: structs containing a Vec<T> would get a different automatic Unpin implementation from that. In this way, adding a trait implementation for an auto trait is somewhat more “breaking” than ordinary trait implementations.
It’s thus currently arguably just a trade-off. Is adding unconditional Unpin to Vecreally valuable? If so, it might be worth the degree of “technically breaking” that such a change would be.
Alternatively, std could probably decide to provide structural pinning for many collection types. That’s a change you can’t make anymore after a move to unconditional Vec<T>: Unpin.
Most benefits of unconditional Vec<T>: Unpin can probably also be gained by just implementing Unpin more generously for the datatype that the Vec<T> is placed into. To help this use-case, if it’s common enough, IDK maybe a helper type
struct NoPin<T: ?Sized>(pub T);
impl<T: ?Sized> Unpin for NoPin<T> {}
// … and perhaps `Deref`+`DerefMut` for convenience
could make sense in std, then you can wrap a local variable or field in that if it matters and you don’t want to write your Unpin implementation manually.
Maybe that’s only kind-of uncommonly needed, either, though.
Most cases where you’d even care about your Unpin implementations are probably using some kind of pin_project, anyway, and in this case, you automatically don’t care about any non-#[pin]-marked fields’ Unpin implementations, anyway.
I can also kind-of imagine a future where structural pinning gets more baked into the language, and then Vec etc can maybe offer pinning projections to its fields even without needing to expand it’s API with lots of new _pinned versions of methods, by making pinning more of a mechanism that’s sprinkled into types parametrically - like lifetimes.[1]
If that becomes the case, it could possibly turn out even more annoying/limiting to have made the change to an unconditional Vec: Unpin.
You're confusing using Box as a pointer (really, Deref-implementing types) and using it as a container.
When used as pointer Box can pin the pointed value because the pointer they contain is stable, i.e. it doesn't move the pointed value when moving them. Vec has no equivalent to Box::pin so it can't do this right now, although it could be provided.
When used as a container (which when pinned would become something like Pin<&mut Box<T>>, where the pointer is instead a mutable reference!) then Box<T> does not structurally pin the T, and in fact you can't get a Pin<&mut T> out of that type because you can instead get a &mut T (you can call get_mut on the Pin thanks to Box<T> being unconditionally Unpin and then get the &mut T from the &mut Box<T>).
With Vec however you can't get a &mut T out of a Pin<&mut Vec<T>>, so an API that allows you to get a Pin<&mut T>could be provided.
!Unpin and we have a method to forward Pin (Pin<&mut Box<T>> to Pin<&mut T>)
Unpin and we don't get to forward Pin (only Pin<Box<T>> to Pin<&mut T>)
For Vec we have currently the downsides of both, so that the choice did not need to be made right at stabilization. I do agree making it Unpin is a hard choice due to auto-trait virality so maybe we can at least get the other side of the coin. If you see Pin<&mut Vec<T>>, does that prove there is no way to cause relocation of the allocation? (I believe so, all methods that may cause it take &mut self which could be used to move the whole Vec). Similar to Box there is interaction with the allocator design for this choice, it must leak the allocation when it is forgotten. Designing the right constraints on the allocator to be forward compatible is what makes this path hard, too.
But to weigh in on which path to prefer, I don't think of Box<[T]> and Vec<T> as equivalent as the initial post suggests. A vector is a partially initialized buffer, not a fully initialized buffer. Pre-allocating buffer space that is only filled later would be valued design space. We could imagine that Vec: !Unpin, besides projection, still provides some methods that modify the content, as long as they do not cause relocation or allow removing existing items. (That is, as long as we restrict pin projection to the initialized portion of the vector).