Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
tyilo opened this issue Jan 20, 2025 · 1 comment
Open
Labels
C-enhancement Category: A new feature or an improvement for an existing one

Comments

@tyilo
Copy link

tyilo commented Jan 20, 2025

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)),
                    }));
                }
            }
        }
    }
}
@taiki-e
Copy link
Owner

taiki-e commented Jan 23, 2025

I don't have a strong opinion on this API yet, but:

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

This code is unsound because if the drop_in_place panics, the write is not called and can cause a double free.

@taiki-e taiki-e added the C-enhancement Category: A new feature or an improvement for an existing one label Jan 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: A new feature or an improvement for an existing one
Projects
None yet
Development

No branches or pull requests

2 participants