How to implement a `Future` / `Stream` that polls `async fn(&mut self)`?

安稳与你 提交于 2020-08-24 11:24:41

问题


I have the following struct

struct Test;

impl Test {
    async fn function(&mut self) {}
}

I want to implement an std::future::Future (well, actually futures::Stream, but it's basically the same) on Test, that would poll the function. My first try looked something like this

impl Future for Test {
    type Output = ();
    fn poll(self: Pin<&mut self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match self.function() {
            Poll::Pending => Poll::Pending,
            Poll::Ready(_) => Poll::Ready(()),
        }
    }
}

Obviously that didn't work. I have understood that function must be called once, returned Future must be stored somewhere in the struct, and then saved future must be polled. So, I have tried this

struct Test(Option<Box<Pin<dyn Future<Output = ()>>>);
impl Test {
    async fn function(&mut self) {}
    fn new() -> Self {
        let mut s = Self(None);
        s.0 = Some(Box::pin(s.function()));
        s
    }
}

And, well, not unexpectedly that also didn't work

The problem is, after I call function() - I have taken a &mut reference of Test, because of that I can't change the Test variable, and therefore - can't store the returned Future inside the Test.


Update 1:

I have arrived at the most cursed and unsafe solution that I can think of (inspired by this)

struct Test<'a>(Option<BoxFuture<'a, ()>>);

impl Test<'_> {
    async fn function(&mut self) {
        println!("I'm alive!");
    }

    fn new() -> Self {
        let mut s = Self(None);
        // fuck the police
        s.0 = Some(unsafe { &mut *(&mut s as *mut Self) }.function().boxed());
        s
    }
}

impl Future for Test<'_> {
    type Output = ();
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.0.as_mut().unwrap().poll_unpin(cx)
    }
}

I REALLY hope that there is another way


回答1:


Though there are times when you may want to do things similar to what you're trying to accomplish here, they are a rarity. So most people reading this, maybe even OP, may wish to restructure such that struct state and data used for a single async execution are different objects.

But to answer your question, yes it is somewhat possible. Unless you want to absolutely resort to unsafe code you will need to use Mutex and Arc. All fields you wish to manipulate inside async fn function will have to be wrapped inside a Mutex and the function itself will accept an Arc<Self>. See the code block below for an example of this.

I must stress, however, that this is not a beautiful solution and you probably don't want to do this. Depending on your specific case your solution may vary, but my guess of what OP is trying to accomplish while using Streams would be better solved by something similar to this gist that I wrote as well: https://gist.github.com/TimLuq/83a35453405f4c6e0f63fb2a0caa9f6e.

use core::future::Future;
use core::pin::Pin;
use std::sync::Arc;
use std::sync::Mutex;

struct Test {
    state: Mutex<Option<Pin<Box<dyn Future<Output = ()>>>>>,
    // if available use your async library's Mutex to `.await` locks on `buffer` instead
    buffer: Mutex<Vec<u8>>,
}
impl Test {
    async fn function(self: Arc<Self>) {
        for i in 0..16u8 {
            let data: Vec<u8> = vec![i]; // = fs::read(&format("file-{}.txt", i)).await.unwrap();
            let mut buflock = self.buffer.lock().unwrap();
            buflock.extend_from_slice(&data);
        }
    }
    pub fn new() -> Arc<Self> {
        let s = Arc::new(Self {
            state: Default::default(),
            buffer: Default::default(),
        });

        {
            // start by trying to aquire a lock to the Mutex of the Box
            let mut lock = s.state.lock().unwrap();
            // create boxed future
            let b = Box::pin(s.clone().function());
            // insert value into the mutex
            *lock = Some(b);
        } // block causes the lock to be released

        s
    }
}
impl Future for Test {
    type Output = ();
    fn poll(self: std::pin::Pin<&mut Self>, ctx: &mut std::task::Context<'_>) -> std::task::Poll<<Self as std::future::Future>::Output> {
        let mut lock = self.state.lock().unwrap();
        let fut: &mut Pin<Box<dyn Future<Output = ()>>> = lock.as_mut().unwrap();
        Future::poll(fut.as_mut(), ctx)
    }
}



回答2:


I'm not sure what you want to achieve and why, but I suspect that you're trying to implement Future for Test based on some ancient tutorial or misunderstanding and just overcomplicating things.

You don't have to implement Future manually. An async function

async fn function(...) {...}

is really just syntax sugar translated behind the scenes into something like

fn function(...) -> Future<()> {...}

All you have to do is to use the result of the function the same way as any future, e.g. use await on it or call block a reactor until it's finished. E.g. based on your first version, you can simply call:

let mut test = Test{};
test.function().await;

UPDATE1

Based on your descriptions I still think you're trying to overcomplicate this minimal working snippet without the need to manually implement Future for anything:

async fn asyncio() { println!("Doing async IO"); }

struct Test {
    count: u32,
}

impl Test {
    async fn function(&mut self) {
        asyncio().await;
        self.count += 1;
    }
}

#[tokio::main]
async fn main() {
    let mut test = Test{count: 0};
    test.function().await;
    println!("Count: {}", test.count);
}


来源:https://stackoverflow.com/questions/61295176/how-to-implement-a-future-stream-that-polls-async-fnmut-self

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!