Hello! I'm making a future to asynchronously return a value of some type, and I'd like to optimize it so that if the value is ready to be returned when the future is created. The future looks, as a minimal example, like this:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
pub enum Foo<T> {
Pending,
Ready(Option<T>),
Complete,
}
impl<T> Foo<T> {
pub fn pending() -> Self {
Self::Pending
}
pub fn ready(output: T) -> Self {
Self::Ready(Some(output))
}
}
impl<T> Future for Foo<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
let me = unsafe { self.get_unchecked_mut() };
match me {
Self::Pending => todo!("This is a toy example"),
Self::Ready(output) => {
let output = output.take().expect("F in ready state twice");
*me = Self::Complete;
Poll::Ready(output)
}
Self::Complete => panic!("Future polled again after completing"),
}
}
}
Is this safe? I believe it is because T will be one of:
- A type that is Unpin,
- A Pinned pointer to a type that is Unpin,
- A type that is !Unpin but is NOT currently pinned, or
- A Pinned pointer to a type that is !Unpin.
Scenarios 1 and 2 are trivially safe, and I believe scenarios 3 and 4 are safe as well. In scenario 3, the type is not currently pinned and cannot rely on the guarantees Pin offers. In scenario 4, the pin contains a pointer to the !Unpin type but does not contain the type itself. It's not safe to move the struct, but moving the Pin is safe.
The only situation I am worried about is a Pin
that contains not a pointer to a !Unpin
type but a !Unpin
type itself. That is, not a Pin<&mut PhantomPinned>
but a Pin<PhantomPinned>
. I don't believe that this pattern is possible/allowable because of things like std::future::Ready
being Unpin
for all T
, not only for T: Unpin
, but I don't know for certain that it's impossible.