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)
byNone
, or callingVec::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 typefn(Pin<&mut Wrapper<T>>) -> Option<T>
, that operation can be used to move aT
out of a pinnedWrapper<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?
Rust can automatically tell which types are safe to move (and will auto impl the Unpin trait for them). If you have a Pin -ned pointer to some data, Rust can guarantee that nothing unsafe will happen (if it's safe to move, you can move it, if it's unsafe to move, then you can't).
1.33.0 · source · [−] pub auto trait Unpin { } Types that can be safely moved after being pinned. Rust itself has no notion of immovable types, and considers moves (e.g., through assignment or mem::replace ) to always be safe. The Pin type is used instead to prevent moves through the type system.
To poll futures, they must be pinned using a special type called Pin<T> . If you read the explanation of the Future trait in the previous section "Executing Future s and Tasks", you'll recognize Pin from the self: Pin<&mut Self> in the Future::poll method's definition.
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::deref
s 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 aPin<P<Wrapper<Field>>>
, by "projecting" ourPin<P<_>>
from theWrapper
to itsfield
?
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 deref
s 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>>
toPin<&[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.
Pin
projection to that fieldFor 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
.
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.
Pin
projection to that fieldFor 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.
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.
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
edit: This answer is incorrect. It remains here for posterity.
Let's begin by recalling why Pin
was introduced in the first place: we want to statically ensure that self-referential futures cannot be moved, thus invalidating their internal references.
With that in mind, let's take a look at the definition of Map
.
pub struct Map<Fut, F> { future: Fut, f: Option<F>, }
Map
has two fields, the first one stores a future, the second stores a closure which maps the result of that future to another value. We wish to support storing self-referential types directly in future
without placing them behind a pointer. This means that if Fut
is a self-referential type, Map
cannot be moved once it is constructed. That is why we must use Pin<&mut Map>
as the receiver for Future::poll
. If a normal mutable reference to a Map
containing a self-referential future was ever exposed to an implementor of Future
, users could cause UB using only safe code by causing the Map
to be moved using mem::replace
.
However, we don't need to support storing self-referential types in f
. If we assume that the self-referential part of a Map
is wholly contained in future
, we can freely modify f
as long as we don't allow future
to be moved.
While a self-referential closure would be very unusual, the assumption that f
be safe to move (which is equivalent to F: Unpin
) is not explicitly stated anywhere. However, we still move the value in f
in Future::poll
by calling take
! I think this is indeed a bug, but I'm not 100% sure. I think the f()
getter should require F: Unpin
which would mean Map
can only implement Future
when the closure argument is safe to be moved from behind a Pin
.
It's very possible that I'm overlooking some subtleties in the pin API here, and the implementation is indeed safe. I'm still wrapping my head around it as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With