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
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:
The last few chapters in the Tokio tutorial, starting from Async in depth
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.
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.
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.
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.
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.