In my quest to understand asynchronous Rust better, I had a look at the asynchronous reader and writer traits that Tokio provides.
When I wanted to pass an asynchronous reader or writer to a generic function, I noticed that the methods I need are only implemented if the reader or writer is Unpin
, see AsyncReadExt::read
and AsyncWriteExt::write
.
Why is that, and what do I need to do about it?
- Declare my function as:
async fn myfunc<W: AsyncWrite + Unpin>
- Use
tokio::pin!
What is the right thing to do?
The following code seems to work:
use tokio::io::AsyncWriteExt as _;
async fn myfunc<W>(writer: &mut W) -> tokio::io::Result<()>
where
W: tokio::io::AsyncWrite,
W: std::marker::Unpin,
{
writer.write(b"ABC\n").await?;
Ok(())
}
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
myfunc(&mut tokio::io::stdout()).await?;
Ok(())
}
Naïvely trying to use tokio::pin!
on the writer (reference) does NOT work:
async fn myfunc<W>(writer: &mut W) -> tokio::io::Result<()>
where
W: tokio::io::AsyncWrite,
{
tokio::pin!(writer);
writer.write(b"ABC\n").await?;
Ok(())
}
I get: error[E0277]: `W` cannot be unpinned
I assume that is because writer is a mutable reference, and not the writer itself (that needs to be Unpin
).
If I declare the function to consume the writer, the code compiles and works fine:
use tokio::io::AsyncWriteExt as _;
async fn myfunc<W>(writer: W) -> tokio::io::Result<()>
where
W: tokio::io::AsyncWrite,
{
tokio::pin!(writer);
writer.write(b"ABC\n").await?;
Ok(())
}
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
myfunc(&mut tokio::io::stdout()).await?;
Ok(())
}
Note that I can still pass a &mut
reference to myfunc
; I guess that's because &mut T
, where T: ?Sized + AsyncWrite + Unpin
, automatically implements AsyncWrite
as well.
What is the correct way to write myfunc
here? Expect the writer to be Unpin
or consume the writer and pin it myself? I did not find any documentation yet on these issues. I also don't understand the reasons why AsyncWriteExt
is only implemented for async writers that are Unpin
.
Can someone enlighten me or tell me what's the idiomatic way of passing an async reader or writer to a function that needs to use the AsyncReadExt
or AsyncWriteExt
methods?
Is this issue documented somewhere?