I need to implement AsyncRead and AsyncWrite but dont know where to get started

Hi folks.
I am working on a UdpStream api to provide a analogous interface to TcpStream for a udp socket for client session management over udp.
I want to implement AsyncRead and AsyncWrite for my struct which has its own read and write methods.
If it was sync code I would simply call self.read and self.write in the impl Read and impl Write respectively. However I dont have much understanding on the basics of the Futures and the docs of tokio about implementing asyncread/write are wip at this stage.

The poll_read function takes whole bunch of things and returns a Poll.
So basically I am trying to understand how to go from async fn self.read -> Result<_,_> to Poll

Thanks

3 Likes

You are getting into hard-mode async Rust now, so I can't give a comprehensive answer in a text box, but I can link some resources you might find useful:

  1. The last few chapters in the Tokio tutorial, starting from Async in depth
  2. Pin and suffering
  3. The os-phil post on Async/Await

The last post is pretty long, but it goes in a lot of detail on how async/await fits together.

Depending on what you need, it may be possible to combine tokio_util::udp with tokio_util::io::StreamReader and sidestep the poll stuff entirely.

9 Likes

the thing is that I already have a method doing the async read part cant I just call somehow Future::poll(self.read(),cx) and get the thing. When I try to do that I get an error saying found opaque type Future expected Pin.

Isnt there any way to use an existing Future without implementing the future itself?
What I dont understand is that I DO have a future already why is it so difficult to just use the poll method of that

If you have a future object you want to poll, you need to store the future as a field in your struct, and repeatedly call poll on it every time your wrapper is polled. You might want to check out the source of PollSemaphore, which does exactly this.

Note that ready!(expr) expands to this:

match expr {
    Poll::Ready(value) => value,
    Poll::Pending => return Poll::Pending,
}

A common mistake is to recreate the future object every time you are polled. This wont work. You must keep the future object around from poll to poll.

Oh that makes a lot of sense however still How do I get from impl Future opaque type to a Pin? and Is there a simpler trait which will simply allow me to call self.read().await and handle the future by itself?

Most people use the following type to store their futures:

Pin<Box<dyn Future<Output = ...> + Send>>

The futures crate has an alias for this called BoxFuture. You can turn an impl Future into a box of this type by using the Box::pin constructor. Once you have a pinned box, you can call .as_mut() on it to get a Pin<&mut TheFuture>, which you can then call poll on.

The example I linked uses ReusableBoxFuture instead, but this is just an optimization. It provides the same features as a boxed future.

5 Likes

but do I have to store my future and poll it myself which will add a lot of complexity to my struct. Isnt there a trait that I can just call my implemented future ? alternatively I think I can create my own trait it will be much simpler I believe.

There's no utility that can do this exact thing for you, no. As I said, poll functions are hard-mode async Rust.

You can definitely create your own trait, but it wont work with things that need you to implement the standard IO traits.

@alice if I were to store my own it would have to be something like

struct Foo{
currentFut:Arc<Mutex<Option<Pin<Box<dyn Future<Output = ...> + Send>>>>
}

right ?

I don't think you need the Arc/Mutex because you shouldn't be sharing the future.

then wouldnt my entire struct be !Send !Sync ? I feel like I am missing a real good chunk of the entire async await deal and how it fits my needs

No, your struct should be Send. What makes you think that it wouldn't be?

If it's because it has a dyn Trait, then that's why I put a + Send in it. Typically you don't need futures to be Sync.

Oh yes it would be Send but not Sync since the Future as a value would be sent to another thread if it was needed. I think I understand what to do now. Lastly what is your opinion about rolling my own AsyncRead/Write traits using async_trait to make things much simpler?
Would you advice against it ?

If you are in a situation where you don't actually need to implement the general IO traits because you are only using the object in ways where that is unnecessary, then that is usually the better solution.

Though I will say that when you don't need to implement the general IO traits, you can usually have the methods be completely ordinary methods instead of trait methods. It is always simpler to avoid async_trait if you don't need it.

use async_trait::async_trait;
use tokio::io::{AsyncRead, AsyncWrite};
#[async_trait]
pub trait WorkerProvider: Send + Sync {
    type Worker: Worker;
    async fn get(&self) -> Option<Self::Worker> {
        None
    }
}
#[async_trait]
pub trait Worker: Send + Sync {
    async fn handle<RW>(&self, rw: RW)
    where
        RW: AsyncRead + AsyncWrite + Send + Sync + 'static + Unpin;
}

This is what my code looks like this is for the refactor of a series of projects using different transports to unify the transport layer and just to split the worker and store logic which doesnt care about the underlaying transport as long as they implement AsyncRead and AsyncWrite

Fair enough. The main disadvantage of rolling your own IO traits here is that async_trait is inherently a lot more expensive than traits based on poll methods, as you incur an allocation cost every time you call a method on the trait.

Besides that, there's no problem if it can do the things it need to do using your own traits.

One of the reasons I recommended using ordinary methods over async_trait above is that this would not be subject to these extra costs.

I think that is for now a little too advanced for me It will require me to implement Future by myself for every type right ?

Which thing would require that?

using poll methods instead of async_trait