-1

How to implement Futures::poll the the following code that will call async method with full ownership to the self?

use anyhow::Error;
use futures::Future;
use futures::channel::oneshot;
use futures::task::{Context, Poll};
use std::pin::Pin;

struct MyLongTask {
    rx: oneshot::Receiver<()>,
}

impl MyLongTask {
    // The method and full ownership to `self` is important to keep!
    async fn recv(mut self) -> Result<(), Error> {
        self.rx.await.map_err(|_| Error::msg("can't recv"))
    }
}

// TryFuture not necessary here
impl Future for MyLongTask {
    type Output = Result<(), Error>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        todo!("how to `self.recv().await` here?")
    }
}

fn main() { }

Playground if needed.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
DenisKolodin
  • 13,501
  • 3
  • 62
  • 65
  • What's the benefit of making a `MyLongTask` future over just calling `rx.await.map_err(...)` directly? What's the goal? – kmdreko Mar 01 '21 at 20:54
  • That's the simplified example of the struct with many many fields that have to be consumed. – DenisKolodin Mar 01 '21 at 23:30
  • If `MyLongTask` has many fields and needs to be consumed and processed by `recv`, why make `MyLongTask` itself a future? `my_long_task.recv()` already returns a future that does what you want, no? – kmdreko Mar 02 '21 at 00:13

1 Answers1

2

You can't call self.recv() inside poll firstly because it does not own self, and secondly because it is not async. Future::poll is synchronous but must return quickly regardless of whether a return value is ready (that's the whole idea of Poll::Pending). In your case you should simply delegate the poll to self.rx: (playground)

impl Future for MyLongTask {
    type Output = Result<(), Error>;
    
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match Pin::new(&mut self.rx).poll(cx) {
            Poll::Ready(x) => Poll::Ready(x.map_err(|_| Error::msg("can't recv"))),
            Poll::Pending => Poll::Pending,
        }
    }
}

Now instead of using task.recv().await, you can just do task.await. I'd also suggest you either implement Future or provide the recv method. Otherwise you could run into trouble later when you change one implementation and forget to change the other.

apilat
  • 1,370
  • 8
  • 17
  • 1
    *"and secondly because it is not `async`"* - to be pedantic you can *call* an `async` function from a non-`async` one, you just can't *`.await`* it. The returned future just won't do anything without something driving it forward. – kmdreko Mar 02 '21 at 00:54
  • It's not exactly what I needed, but also helpful. – DenisKolodin Mar 02 '21 at 11:06