Skip to content

Add a take_unpinned method: Pin<&mut Option<S>> to Option<SProjectionOwned> #359

Open
@tyilo

Description

@tyilo

I think pin-project could provide a safe method from Pin<&mut Option<S>> to Option<SProjectionOwned> which is a mix of Option::take, Pin::set(..., None) and project_replace. I imagine it could look something like this:

#[pin_project(take_unpinned)]
struct Inner<Pinned, Unpinned> {
  #[pin]
  pinned: Pinned,
  unpinned: Unpinned,
}

#[pin_project]
struct Outer<Pinned, Unpinned> {
  #[pin]
  inner: Option<Inner<Pinned, Unpinned>>,
}

// Generated method
impl<Pinned, Unpinned> Inner<Pinned, Unpinned> {
  fn take_unpinned(this: Pin<&mut Option<Self>>) -> Option<InnerProjectionOwned> {
    // - Check if this points to None and bail out.
    // - Drop pinned fields in place
    // - Ptr read unpinned fields
    // - Replace this with None using ptr write
    // - Return unpinned fields
    todo!()
  }
}

The following is an example where this could be useful (here take_output is what pin_project could provide a generic version of):

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

use pin_project::pin_project;

#[pin_project]
struct DelayOutput<T, Fut: Future<Output = T>> {
    #[pin]
    fut: Fut,

    #[pin]
    waiting: Option<DelayOutputInner<T>>,
}

impl<T, Fut: Future<Output = T>> DelayOutput<T, Fut> {
    fn new(fut: Fut) -> Self {
        Self { fut, waiting: None }
    }
}

#[pin_project]
struct DelayOutputInner<T> {
    output: T,
    #[pin]
    sleep: tokio::time::Sleep,
}

impl<T> DelayOutputInner<T> {
    fn take_output(mut this: Pin<&mut Option<Self>>) -> Option<T> {
        let (inner_ptr, opt_ptr) = {
            let opt_ref = unsafe { this.as_mut().get_unchecked_mut() };
            let Some(inner_ref) = opt_ref else {
                return None;
            };
            let ptrs = (&raw mut *inner_ref, &raw mut *opt_ref);
            _ = this;
            ptrs
        };

        let output = unsafe { (&raw mut (*inner_ptr).output).read() };
        unsafe { (&raw mut (*inner_ptr).sleep).drop_in_place() };
        unsafe { opt_ptr.write(None) };

        Some(output)
    }
}

impl<T, Fut: Future<Output = T>> Future for DelayOutput<T, Fut> {
    type Output = T;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut this = self.project();
        loop {
            if let Some(waiting) = this.waiting.as_mut().as_pin_mut() {
                match waiting.project().sleep.poll(cx) {
                    Poll::Pending => return Poll::Pending,
                    Poll::Ready(_) => {
                        let output = DelayOutputInner::take_output(this.waiting);
                        return Poll::Ready(output.unwrap());
                    }
                }
            }

            match this.fut.as_mut().poll(cx) {
                Poll::Pending => return Poll::Pending,
                Poll::Ready(output) => {
                    this.waiting.set(Some(DelayOutputInner {
                        output,
                        sleep: tokio::time::sleep(Duration::from_secs(1)),
                    }));
                }
            }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-enhancementCategory: A new feature or an improvement for an existing one

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions