How do I pin project an element of a vector?

1.1k Views Asked by At

Following code does not compile because the element pulled from the vector does not implement the Pin<> type.

The error is in ele.poll() call

#[pin_project]
pub struct FifoCompletions<T, F>
where
    F: Future<Output = Result<T, Error>>
{
    #[pin]
    pending: VecDeque<F>,
}

impl <T, F> FifoCompletions<T, F>
where
    F: Future<Output = Result<T, Error>>
{
    pub fn push(&mut self, f: F) {
        self.pending.push_back(f);
    }
}

impl <T, F> Future for FifoCompletions<T, F>
where
    F: Future<Output = Result<T, Error>>
{
    type Output = Result<T, Error>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        while !this.pending.is_empty() {
            match this.pending.front_mut() {
                None => unreachable!(),
                Some(ele) => {
                    let output = ready!(ele.poll(cx));
                }
            }

        }
        Poll::Pending
    }
}

Error message is

no method named `poll` found for mutable reference `&mut F` in the current scope

method not found in `&mut F`

help: items from traits can only be used if the type parameter is bounded by the traitrustc(E0599)
sm.rs(66, 45): method not found in `&mut F`
1

There are 1 best solutions below

0
Shepmaster On

Here's the literal answer to your question:

use std::{
    future::Future,
    pin::Pin,
    task::{Context, Poll},
};

pub struct Completions<F>
where
    F: Future<Output = ()>,
{
    pending: Vec<F>,
}

impl<F> Future for Completions<F>
where
    F: Future<Output = ()>,
{
    type Output = ();

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        // I copied this from Stack Overflow without reading the prose
        // that describes why this is or is not safe.
        // ... It's probably not safe.
        let first = unsafe { self.map_unchecked_mut(|this| &mut this.pending[0]) };
        first.poll(cx)
    }
}

However, this is very likely to be unsafe and introduce undefined behavior. The problem is that the requirements for pinning are complex and nuanced. Specifically, once pinned, a value may never be moved in memory, including when it is dropped. From the docs, emphasis mine:

This can be tricky, as witnessed by VecDeque<T>: the destructor of VecDeque<T> can fail to call drop on all elements if one of the destructors panics. This violates the Drop guarantee, because it can lead to elements being deallocated without their destructor being called. (VecDeque<T> has no pinning projections, so this does not cause unsoundness.)

I checked with taiki-e, the author of the pin-project crate. A lightly-edited quote:

Operations such as Vec(Deque)'s push, insert, remove, etc. can move elements. If you want to pin elements, these methods should not be called without Unpin after they have been pinned. The pin API actually blocks this.

If the destructor moves elements, it is unsound to pin the element returned by accessors like get_mut, front_mut without Unpin.

Make the element Unpin or use a collection that can handle !Unpin futures like FutureUnordered instead of VecDeque.

Applied directly, the safest way to do this is:

use pin_project::pin_project; // 1.0
use std::{
    future::Future,
    pin::Pin,
    task::{Context, Poll},
};

#[pin_project]
pub struct Completions<F>
where
    F: Unpin,
    F: Future<Output = ()>,
{
    #[pin]
    pending: Vec<F>,
}

impl<F> Future for Completions<F>
where
    F: Unpin,
    F: Future<Output = ()>,
{
    type Output = ();

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        let pending = this.pending.get_mut();
        let first = Pin::new(&mut pending[0]);
        first.poll(cx)
    }
}

See also: