How can one implement a Future that returns a non-cloneable argument that was passed into the function that created it

When relying on the compiler to auto-generate a Future by relying on async fn, the following is possible:

struct Foo;
async fn foo(x: Foo) -> Foo {
    x
}

If I didn't want to rely on the compiler to generate the hidden Future type, how would I go about it? Future::poll takes a Pin<&mut Self>, so I am unable to return data that is owned by Self; therefore the following naive approach obviously won't compile:

use core::{
    future::Future,
    pin::Pin,
    task::{Context, Poll},
};
struct Foo;
fn foo(x: Foo) -> Bar {
    Bar(x)
}
struct Bar(Foo);
impl Future for Bar {
    type Output = Foo;
    fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
        Poll::Ready(self.0)
    }
}
1 Like

The generated data structure for an async block is always essentially an enum with multiple states. In your case, it would be like

enum Bar {
    NotStarted(Foo),
    Completed,
}

When poll() is called, it moves from the NotStarted state to the Completed state, giving up ownership of the Foo. If it is already in the Completed state, it panics (polling a future after it completes is not expected to do anything better than panic, and may do worse).

In your simple case of just having one value and no awaits, you can skip the custom enum and call Option::take() on the contents of a:

struct Bar(Option<Foo>);

In the general case, futures have additional states besides NotStarted and Completed — one per await point, essentially, plus one for if the async code panicked while being polled.

4 Likes

My god, I'm an idiot. Thank you so much for that. Even though I'm still fairly new to async stuff, I should have been able to think of that.

I'm sorry for replying to this after accepting your solution, but how would one implement Bar as an enum with the added requirement that Foo implements !Unpin?

use core::{
    future::Future,
    marker::PhantomPinned,
    mem,
    ops::DerefMut,
    pin::Pin,
    task::{Context, Poll},
};
struct Foo(PhantomPinned);
fn foo(x: Foo) -> Bar {
    Bar::NotStarted(x)
}
enum Bar {
    NotStarted(Foo),
    Completed,
}
impl Future for Bar {
    type Output = Foo;
    fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
        // This does not compile since `Pin::deref_mut` is not defined due to `Foo` not implementing `Unpin`.
        match mem::replace(self.deref_mut(), Self::Completed) {
            Self::NotStarted(foo) => Poll::Ready(foo),
            Self::Completed => panic!("completed future is not allowed to be polled again"),
        }
    }
}

The only way I could get this to work is by relying on Box; however the compiler is able to compile the code when using async fn even in a #![no_std] crate so it clearly does not rely on Box.

Crates like pin-project can create safe APIs to support that.

Most of this is educational so that I can demystify what's going on. As stated, I can just write an async fn and have the compiler construct the appropriate Future for me. I can try using unsafe code like below, however I'd like to know if/how it's safe:

impl Future for Bar {
    type Output = Foo;
    fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
        // SAFETY:
        // Is this actually safe? The documentation states:
        // This function is unsafe. You must guarantee that you will never move the data out of the mutable
        // reference you receive when you call this function, so that the invariants on the `Pin` type can be upheld.
        // However I do move the data via `mem::replace`.
        let bar = unsafe { self.get_unchecked_mut() };
        match mem::replace(bar, Self::Completed) {
            Self::NotStarted(foo) => Poll::Ready(foo),
            Self::Completed => panic!("completed future is not allowed to be polled again"),
        }
    }
}

The thing you have to decide first is: If Bar is currently pinned, then does that imply that Foo is currently pinned too (“structural pinning”)?

  • If Foo is also pinned, then you cannot return Foo (unless Foo: Unpin) because that would be moving Foo while it is pinned. (For example, when a Future contains another Future it polls, it will never return that future, only poll it and eventually drop it.)
  • If Foo is not also pinned, then you must never make a Pin<&mut Foo> unless Foo: Unpin, and either
    • you should unsafe impl Unpin for Bar because nothing in it needs the Pin guarantee, and then you can use Pin::get_mut(), or
    • something else in Bar needs pinning even though foo does not, in which case you are right to unsafely access the foo field.

These are the rules that the pin-project library previously mentioned would be implementing for you, by picking one case or the other based on whether you wrote its #[pin] attribute on a field, and providing accessors that only allow the correct mode of access for that field.

Unpin is not an unsafe trait; thus it doesn't require unsafe. How is this safe?

Whoops, I misremembered. It can be safe because you can't actually make use of the pinning guarantee without some unsafe code that is doing structural pin projection, so if you have such code, it is your responsibility to not write impl Unpin for ....