Method that accepts future pointer

I'd like to write an async function that accepts a future pointer which is then passed into async_std::task::spawn(). The problem I have is how to properly define a future type with mutable stream argument. Here's what I have:

use async_std::net::{TcpListener, TcpStream};

type TcpHandler = fn(stream: TcpStream) -> Pin<Box<dyn std::future::Future<Output = Result<(), std::io::Error>> + Send + Sync + 'static>>;


pub async fn listen(&mut self, handler: TcpHandler) -> Result<(), std::io::Error> {
    let listener = TcpListener::bind("...").await?;

    while let Some(stream) = listener.incoming().next().await {
        let stream = stream?;
        async_std::task::spawn(handler(stream));
    }
    Ok(())
}

Then I would call the listen function like this:

async fn handle(mut stream: TcpStream) -> Result<(), std::io::Error> {
    stream.write_all(b"hello world").await?;
    Ok(())
}

#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
    listen(handle).await?; // NOT WORKING
    Ok(())
}

The async keyword makes the function return an impl Future, which isn't a concrete type. The Pin<Box<dyn Future>> type is suitable for hand written futures and is useful for breaking recursion in async functions.

There are two ways to approach this:

  1. You can make handle return a Pin<Box<dyn Future>>>, that way handle will match the TcpHandler type you've defined:
fn handle(mut stream: TcpStream) -> Pin<Box<dyn std::future::Future<Output=Result<(), std::io::Error>> + Send + Sync + 'static>> {
    Box::pin(async move {
        stream.write_all(b"hello world").await?;
        Ok(())
    })
}
  1. Or you can make listen accept a generic future:
pub async fn listen<R, H>(handler: H) -> Result<(), std::io::Error>
    where
        H: Fn(TcpStream) -> R,
        R: 'static + Send + Future<Output=Result<(), std::io::Error>>
{
    let mut listener = TcpListener::bind("...").await?;

    while let Some(stream) = listener.incoming().next().await {
        let stream = stream?;
        tokio::task::spawn(handler(stream));
    }
    Ok(())
}

Note: I couldn't use play.rust-lang.org with async_std, so I changed the code to work with tokio.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.