Implementors of `Future`

After reading Boats' "Why Async Rust" I've come to notice these (std (permalink)):

#[stable(feature = "futures_api", since = "1.36.0")]
impl<F: ?Sized + Future + Unpin> Future for &mut F {
    type Output = F::Output;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        F::poll(Pin::new(&mut **self), cx)
    }
}
#[stable(feature = "futures_api", since = "1.36.0")]
impl<P> Future for Pin<P>
where
    P: ops::DerefMut<Target: Future>,
{
    type Output = <<P as ops::Deref>::Target as Future>::Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        <P::Target as Future>::poll(self.as_deref_mut(), cx)
    }
}

Specifically I'm interested in the reason behind the F: Unpin trait bound for &mut F to be Future. Is it to guard against something like Arc<Mutex<F: !Unpin + Send + Future>> (playground)?

use std::{
    future::poll_fn, hint::spin_loop, mem::replace, pin::Pin, sync::Arc, task::Poll, time::Duration,
};
use tokio::{
    join, spawn as task_spawn, sync::Mutex, sync::oneshot::channel, task::spawn_blocking,
    time::sleep,
};

#[tokio::main]
async fn main() {
    let fut = Arc::new(Mutex::new(sleep(Duration::from_secs(2))));

    let h0 = {
        let fut = Arc::clone(&fut);
        task_spawn(async move {
            let fut = &mut *fut.lock().await;
            let fut: &mut (dyn Future<Output = ()> + Send) = fut;
            let fut = unsafe {
                // Supposedly the reason why we need `Unpin` trait bound?
                // safety:
                // _not_ safe since it's not local-pinning but some arbitrary reference
                // in particular it's behind `Arc<Mutex<_>>` s.t. other thread may do bad things
                Pin::new_unchecked(fut)
            };
            fut.await;
        })
    };

    let (tx, rx) = channel();
    let mut new_nap = sleep(Duration::from_secs(1));
    poll_fn(|cx| {
        // Stolen from https://fasterthanli.me/articles/pin-and-suffering
        // abusing `tokio::time::Sleep` for demonstration purposes
        //
        // There might exist some other sorts of `!Unpin` `Future` that's even worse?
        // in the sense simply `mem::swap` a fresh, not yet `poll`-ed instance is enough to
        // break certain invariants within the type
        unsafe {
            _ = Pin::new_unchecked(&mut new_nap).as_mut().poll(cx);
        };
        Poll::Ready(())
    })
    .await;
    let h1 = spawn_blocking(move || {
        loop {
            match fut.try_lock() {
                Ok(mut old_nap) => {
                    tx.send(replace(&mut *old_nap, new_nap)).unwrap();
                    break;
                }
                Err(_) => {
                    spin_loop();
                }
            }
        }
    });
    let old_nap = task_spawn(async move { rx.await.unwrap().await });

    _ = join!(h0, h1, old_nap);
}

If that's the case, then I don't understand this quote: I'm assuming &mut dyn Future trait objects here, and from the above experiment, we can see that indeed we do need the Unpin trait bound, no?

The only one that’s really bad... is that you need to pin a future trait object to await it

If you have a &mut F, then you can poll the future, and then move it (e.g. with mem::swap). That's against the Pin contract, thus not allowed. With F: Unpin, the Pin contract does not apply so it's okay.

1 Like

To be more specific, I guess the exact issue here is if we had this below, then we can .await on a &mut impl Future, which implicitly means the executor would Pin it and, as you mentioned, poll it, while the pointee may got moved, and chaos ensues. OTOH manual explicit Future::poll is somewhat ok-ish, though, since in this case it's probably the unsafe block which called Pin::new_unchecked that's at fault. I.e. this version enables implicit Future::poll implied by the .await keyword.

impl<F: ?Sized + Future /* + Unpin */> Future for &mut F {
    type Output = F::Output;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        F::poll(Pin::new(&mut **self), cx)
    }
}

I (think that I) understand this version allows for violation of the pin contract, but then I don't know why requiring future trait objects to be pinned to be .await-ed is considered... bad? Since in this context we do need the Unpin bound.

Even if you use .await, you could violate the pinning contract using &mut F.

let mut fut = ...;
(&mut fut).await;
let fut2 = fut; // OOPS
1 Like

He is referring to design decision; as opposed to how you use the functionality provided.
See the Future from the past. v0.1

1 Like

Sorry I didn't make myself clear, and thx for the (much) more succinct example illustrating the idea. Yes, I understand that lifting the Unpin bound on impl Future for &mut F is easily unsound. I'm more confused about Boats' note, emphasis mine:

The only one that’s really bad (and embarrassing to me personally) is that you need to pin a future trait object to await it. This was an unforced error that would now be a breaking change to fix.

Current behavior (&mut dyn Future is not automatically Future i.e. we cannot .await it with safe Rust) is totally fine; we don't want to accidentally Future::poll a Future that's !Unpin. So to poll a trait object, in modern Rust, either the pointee is Unpin and we write &mut (dyn Future + Unpin), or we try to pin it with unsafe, in which case if anything went south, it's clearly this unsafe block's fault. Which means in general we do need to pin the trait object in modern Rust in order to Future::poll it, unless we get lucky and pointee is Unpin. How is something 'bad' if it's required for memory safety?

Thx for the link. I'll take a look later for it's late night here.

My read on the quote is that it is saying pinning is bad for ergonomics because the type needs to be Unpin anyway. You pin it as a bit of unnecessary ceremony.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.