As far as I understand the point of Pin<T>
is to have a pointer to T, that we know will point to the same location in memory, though out the life time of the pointer. With that said I was wondering if there is any behavioral difference between Box<T>
, and Pin<Box<T>>
, as I'm just unable to come up with a minimal example of when one would fail to do something that the other can.
You can use Pin::into_inner and Pin::new to transform them to each other in safe code with zero cost. So, no, there's no real difference.
So my assumption would be that the reason you might want to have Pin<Box<T>>
instead of Box<T>
is to just be able to pass Box<T>
somewhere that takes in Pin<T>
.
Thank you for your reply that they aren't different in any way behavior wise!
There are no difference for any Unpin
type. And Box
is Unpin for any inner type.
Pin::new
requires <Ptr as Deref>::Target: Unpin
though. It's trivially true that Box<T>: Unpin
but you also need T: Unpin
.
To see a difference between the two you need an unpinned type such as Notified
or Sleep
in tokio. You can't poll a Box<Sleep>
. The documentation in tokio
goes into some details here.
Pin::into_inner
only works when T
(not the Box
) is Unpin
. This is necessary to uphold the Drop
guarantee. In practial terms, this means that there's no safe way to swap the contents of Pin<Box<T>>
with a different instance of T
, unless T:Unpin
.
But wouldn't the location to which Pin<Box<T>>
points stay the same if you swap the T
, because from what I know it should, and there for it should be a valid operation.
One of Pin
's main purposes is to allow types to contain self-referential pointers. For the sake of argument, let's say this was valid:
let mut node: Pin<Box<T>> = ...;
// Do stuff involving `node`...
let new_node: T = ...;
let old_node: T = std::mem::replace(&mut *node, new_node);
At this point, all of the pointers that old_node
expects to be internal implementation details are now pointing into the new_node
instance instead. This has the potential to cause all sorts of problems, but I'll just point out one scenario here:
When old_node
gets dropped, it may use those "internal" pointers to clean up some of its state on the assumption that they're now unreachable. But, because they're pointing at where old_node
used to be, this instead corrupts new_node
's internal state.
Yes, that's why you can't swap them: if you could swap two T
s behind Pin<Box<T>>
, the Pin<Box>
es keep pointing to the same address, but the T
s are now different, which is exactly the thing Pin
is supposed to prevent (when T: !Unpin
).
Consider if T
is self-referential: it's unsound to move it to a different Box
because the moved T
will still point to the old Box
.
I think quoting the drop guarantee is confusing here, because it's very subtle and not the most relevant point here. Pin::into_inner
could often be used to move the pointer's target (especially when there's DerefMut
[1] ), so the Unpin
requirement is much simpler in just enforcing the basic guarantees that pinned things (pointee of pinned pointer[2]) must not be moved out of its memory location.
Got it thanks! Didn't occur to me to allocate new memory on the heap, and preform a shallow copy in to it, to cause a failure.
Pin
is confusing because it's super generalized meta API. It transforms a pointer type to another pointer type, generally. Probably monad transformers are confusing people for the same reason..
In a simpler world approach (IIRC that actually mirrors the earlier designs of these APIs) you'd just define some PinMut<'a, T>
variant of &'a mut T
for a "pinned mutable pointer", and Future::poll
can take PinMut<'a, Self>
as an argument.
It's the realization that you'd also want some PinBox<T>
pinned variant of Box<T>
, and maybe some rarer times pinned immutable references or more.. that the design of a generic wrapper came up that respells these Pin<&'a mut T>
and Pin<Box<T>>
.
Maybe it is an instance of unnecessary over-generalization, doing it because it could be done[1]; but even just between boxes and mutable references there's a relatively nontrivial API surface that would otherwise be duplicated (and they'd still interact with each other e. g. with Pin::as_mut
, and their guarantees/contract are still strongly related, and Unpin
would also still exist).
Nonetheless, as the average user, one should probably start out by just thinking about the concrete cases or Pin<Box<T>>
and Pin<&'a mut T>
.
Maybe we should define type synonyms for these and see how much more readable the documention could become when all the type signatures are specialized to these two cases? (I think rustdoc is even capable of doing this work - of generating a copy of the docs with concrete types filled in for the method signatures - to some degree already.)
Maybe Rustdoc should even learn to generate specialized versions of API docs with specific trait bounds known to be met, or broken - i. e. for T: Unpin
hiding those bounds, or for T: !Unpin
hiding the methods that become unusable from that
well, at least seemed like it could be done, but I might know a thing or two about some annoyingly hard to fix shortcomings/oversights ↩︎