When is it safe to move a member value out of a pinned future?

#1

I’m cross-posting my question from Stack Overflow in the hopes that someone knowledgeable about pinning and futures has a chance to see it:

If it’s relevant, there’s a 250 point bounty on the question.

1 Like
#2

@Nemo157 provided some information in Discord, but I’m still hoping for a complete, cohesive answer…

nemo157

@shepmaster the key for Map is that a Pin<&mut F> is never observed, so the f field is never considered to have been pinned

shepmaster

But I don’t know what you mean by “observed” here

nemo157

The Map type has chosen to propagate the pinning guarantees to its future field, but not to its f field

Which you can also see with the conditional Unpin implementation

shepmaster

Why is it allowed to make that choice about guarantees?

nemo157

Map::new takes an F by value, so it’s definitely not pinned there, after that Map has ownership of the F but never allows construction of a pinned reference to it

shepmaster

Couldn’t the owner of Map have pinned the entire thing, which means that F has been pinned at some point?

And the whole “once pinned forever pinned”?

nemo157

If nothing ever sees a pinned reference to the F then it’s not considered pinned, even if the Map is pinned it has ownership of the F and privacy boundaries stop anything else constructing a reference to one of its fields

shepmaster

“If nothing ever sees a pinned reference to the F then it’s not considered pinned” — is that documented anywhere?

nemo157

I guess it’s not explicitly stated anywhere, it implicitly comes from Pin<P> being the type that provides the “pinning invariants”, so if you’ve not seen one you haven’t been provided those invariants to rely on

So by taking in Pin<&mut Self> in <Map as Future>::poll Map has been provided those invariants, but it has chosen to not pass them along to F ever

#3

That is what structural pinning is about:

First, I will note P<T> something like impl Deref<Target = T>, that is, some (smart) pointer type P that Deref::derefs to a T (Pin only “applies” to / make sense on such (smart) pointers).

Let’s say we have:

struct Wrapper<Field> {
    field: Field,
}

Now, the question is, whether we can get a Pin<P< Field >> from a Pin<P< Wrapper<Field> >>, by “projecting” our Pin<P<_>> from the Wrapper to its field.

This already requires the basic projection P<Wrapper<Field>> -> P<Field>, which is only even possible for

  • shared references: P<T> = &T (this is not a very interesting case given that Pin<P<T>> always derefs to T)

  • unique references: P<T> = &mut T.

I will note this &[mut] T

So, the question is:

Can we go from Pin<&[mut] Wrapper<Field>> to Pin<&[mut] Field>?

The point that may still be unclear in the documentation is the following: it is up to the creator of Wrapper to decide!

So there are two possible choices for the library author, regarding each one of the struct fields:

  • either there is a structural Pin projection to that field;
    (for instance, when the ::pin_utils::unsafe_pinned! macro is used to define such projection)

    Then, for the Pin projection to be sound:

    • the whole struct must only implement Unpin when all the fields for which there is a structural Pin projection implement Unpin,

      • Thus, no implementation is allowed to use unsafe to move such fields out of a Pin<&mut Wrapper<Field>> (or Pin<&mut Self> when Self = Wrapper<Field>); for instance, Option::take() is forbidden.
    • the whole struct may only implement Drop if Drop::drop does not move any of the fields for which there is a structural projection,

    • the struct cannot be #[repr(packed)] (a corollary of the previous item).

    • In your given future::Map example, this is the case of the future field of the Map struct.

  • or there is no Pin projection to that field;

    In that case, that field is not considered pinned! (by a Pin<&mut Wrapper<Field>>)

    • thus whether Field is Unpin or not, does not matter;

      • implementations are allowed to use unsafe to move such fields out of a Pin<&mut Wrapper<Field>>; for instance, Option::take() is allowed.
    • and ::pin_utils::unsafe_unpinned! is safe to use to define a Pin<&mut Wrapper<Field>> -> &mut Field projection.

    • Drop::drop is also allowed to move such fields,

    • In your given future::Map example, this is the case of the f field of the Map struct.