Is there any different between Box<T>, and Pin<Box<T>>

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.

1 Like

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.

1 Like

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.

6 Likes

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.

4 Likes

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.

3 Likes

Yes, that's why you can't swap them: if you could swap two Ts behind Pin<Box<T>>, the Pin<Box>es keep pointing to the same address, but the Ts 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.

3 Likes

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.


  1. with &mut T access, you can do things like mem::swap already ↩︎

  2. unless Unpin or you know the implementation details that motivate the pinning for a concrete type and know what you're doing ↩︎

2 Likes

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 :thinking:


  1. 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 ↩︎

10 Likes