How to use the pin! macro (or Box::pin()?) in an async function that takes a trait as input parameter?

I have a function that looks like:

pub async fn read_timeout(stream: &mut TcpStream, buffer: &mut [u8], timeout_secs: u64) -> Result<usize, IoError> {
    let duration = Duration::from_secs(timeout_secs);
    match timeout(duration, stream.read(buffer)).await {
        Ok(result) => match result {
            Ok(read_length) => Ok(read_length),
            Err(error) => Err(error),
        },
        Err(error) => Err(IoError::new(ErrorKind::TimedOut, error)),
    }
}

Now, I would like to extend this function to accept TcpStream or File, and maybe other types that implement the AsyncReadEx trait:

pub async fn read_timeout(stream: &mut impl AsyncReadExt, buffer: &mut [u8], timeout_secs: u64) -> Result<usize, IoError> {
    let duration = Duration::from_secs(timeout_secs);
    match timeout(duration, stream.read(buffer)).await {
        Ok(result) => match result {
            Ok(read_length) => Ok(read_length),
            Err(error) => Err(error),
        },
        Err(error) => Err(IoError::new(ErrorKind::TimedOut, error)),
    }
}

But this does not compile! :slightly_frowning_face:

It says that AsyncReadEx does not implement Unpin and that I'm supposed to use the pin! macro or Box::pin(). But I don't know how to use the pin! macro. The examples haven't really been helpful for my situation. I also tried using Box::pin(), but this still does not work:

pub async fn read_timeout(stream: &mut impl AsyncReadExt, buffer: &mut [u8], timeout_secs: u64) -> Result<usize, IoError> {
    let duration = Duration::from_secs(timeout_secs);
    let pinned_stream = Box::pin(stream);
    match timeout(duration, pinned_stream.read(buffer)).await {
        Ok(result) => match result {
            Ok(read_length) => Ok(read_length),
            Err(error) => Err(error),
        },
        Err(error) => Err(IoError::new(ErrorKind::TimedOut, error)),
    }
}

Any hints would be very welcome! :sweat_smile:

If you only have a mutable reference, then the caller must do the pinning.

pub async fn read_timeout(stream: Pin<&mut impl AsyncRead>, buffer: &mut [u8], timeout_secs: u64) -> Result<usize, IoError> {
    let duration = Duration::from_secs(timeout_secs);
    match timeout(duration, stream.read(buffer)).await {
        Ok(result) => match result {
            Ok(read_length) => Ok(read_length),
            Err(error) => Err(error),
        },
        Err(error) => Err(IoError::new(ErrorKind::TimedOut, error)),
    }
}

Also, never use the Ext traits in function signatures.

1 Like

If you only have a mutable reference, then the caller must do the pinning.

Thanks. But how exactly would I do that?

(assuming that caller usually has, e.g., a &mut TcpStream or a &mut File)

Also, never use the Ext traits in function signatures.

Why? Is there a better alternative?

Yes. The trait AsyncReadExt follows the extension trait pattern, it’s implemented automatically for all types that implement AsyncRead, in order to offer some additional trait methods. This means, if you just use the AsyncRead trait in signatures, e.g. T: AsyncRead or impl AsyncRead, then you can still access the methods from the extension trait. @alice gives a modified version of the code that already does this.


The perhaps more convenient alternative is to (mirror the APIs in AsyncReadExt and) just add an Unpin restriction. It’s not really restricting much; as users with non-Unpin types can still pass something like &mut Pin<&mut T>> or so anyway.

pub async fn read_timeout(
-   stream: &mut impl AsyncReadExt,
+   stream: &mut (impl AsyncRead + Unpin),
    buffer: &mut [u8],
    timeout_secs: u64,
) -> Result<usize, IoError> {
    let duration = Duration::from_secs(timeout_secs);
    match timeout(duration, stream.read(buffer)).await {
        Ok(result) => match result {
            Ok(read_length) => Ok(read_length),
            Err(error) => Err(error),
        },
        Err(error) => Err(IoError::new(ErrorKind::TimedOut, error)),
    }
}

Rust Playground

2 Likes
stream: &mut (impl AsyncRead + Unpin)

:person_facepalming:t2: Sometimes the solution is so simple, but you just don't see it.

Thanks!

Yes. The trait AsyncReadExt follows the extension trait pattern, it’s implemented automatically for all types that implement AsyncRead, in order to offer some additional trait methods. This means, if you just use the AsyncRead trait in signatures, e.g. T: AsyncRead or impl AsyncRead, then you can still access the methods from the extension trait.

Oh, I see.