Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
621 views
in Technique[技术] by (71.8m points)

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

I'm writing a future combinator that needs to consume a value that it was provided with. With futures 0.1, Future::poll took self: &mut Self, which effectively meant that my combinator contained an Option and I called Option::take on it when the underlying future resolves.

The Future::poll method in the standard library takes self: Pin<&mut Self> instead, so I've been reading about the guarantees required in order to safely make use of Pin.

From the pin module documentation on the Drop guarantee (emphasis mine):

Concretely, for pinned data you have to maintain the invariant that its memory will not get invalidated from the moment it gets pinned until when drop is called. Memory can be invalidated by deallocation, but also by replacing a Some(v) by None, or calling Vec::set_len to "kill" some elements off of a vector.

And Projections and Structural Pinning (emphasis mine):

You must not offer any other operations that could lead to data being moved out of the fields when your type is pinned. For example, if the wrapper contains an Option<T> and there is a take-like operation with type fn(Pin<&mut Wrapper<T>>) -> Option<T>, that operation can be used to move a T out of a pinned Wrapper<T> -- which means pinning cannot be structural.

However, the existing Map combinator calls Option::take on a member value when the underlying future has resolved:

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
    match self.as_mut().future().poll(cx) {
        Poll::Pending => Poll::Pending,
        Poll::Ready(output) => {
            let f = self.f().take()
                .expect("Map must not be polled after it returned `Poll::Ready`");
            Poll::Ready(f(output))
        }
    }
}

The f method is generated by the unsafe_unpinned macro and looks roughly like:

fn f<'a>(self: Pin<&'a mut Self>) -> &'a mut Option<F> {
    unsafe { &mut Pin::get_unchecked_mut(self).f }
}

It appears that Map violates the requirements that are described in the pin documentation, but I believe that the authors of the Map combinator know what they are doing and that this code is safe.

What logic allows them to perform this operation in a safe manner?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It is all about structural pinning.

First, I will use the syntax P<T> to mean something like impl Deref<Target = T> — some (smart) pointer type P that Deref::derefs to a T. Pin only "applies" to / makes sense on such (smart) pointers.

Let's say we have:

struct Wrapper<Field> {
    field: Field,
}

The initial question is

Can we get a Pin<P<Field>> from a Pin<P<Wrapper<Field>>>, by "projecting" our Pin<P<_>> from the Wrapper to its field?

This requires the basic projection P<Wrapper<Field>> -> P<Field>, which is only 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 use the syntax &[mut] T for this type of projection.

The question now becomes:

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

The point that may be unclear from the documentation is that it is up to the creator of Wrapper to decide!

There are two possible choices for the library author for each struct field.

There is a structural Pin projection to that field

For instance, the pin_utils::unsafe_pinned! macro is used to define such a projection (Pin<&mut Wrapper<Field>> -> Pin<&mut Field>).

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.

    • 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.

There is no structural Pin projection to that field

For instance, the pin_utils::unsafe_unpinned! macro is used to define such a projection (Pin<&mut Wrapper<Field>> -> &mut Field).

In this case, that field is not considered pinned by a Pin<&mut Wrapper<Field>>.

  • 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.
  • 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.

Example of both types of projection

impl<Fut, F> Map<Fut, F> {
    unsafe_pinned!(future: Fut); // pin projection -----+
    unsafe_unpinned!(f: Option<F>); // not pinned --+   |
//                                                  |   |
//                 ...                              |   |
//                                                  |   |
    fn poll (mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
        //                                          |   |
        match self.as_mut().future().poll(cx) { // <----+ required here
            Poll::Pending => Poll::Pending, //      |
            Poll::Ready(output) => { //             |
                let f = self.f().take() // <--------+ allows this

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...