Understanding rust's opaque types

i am making a personal server, and I am trying to add an event handler to the server.
the function that contains the server is this:

pub struct Server{/*inner fields*/}
impl Server{
pub async fn serve<F>(self, handle_stream: F) -> i32
    where
    F: FnOnce(&mut TcpStream){return 0;}
}

(i removed the rest of the code, because it doesn't affect this issue).

since the server is async, i wanted to make the function input async as well. according to this stackoverflow question I should be able to just call the function output future and that should be enough.
but after changing the function into this:

impl Server{
    pub async fn serve<F, Fut>(self, handle_stream: F) -> i32
    where
    F: FnOnce(&mut TcpStream)->Fut,
    Fut: Future<Output=()>{return 0;}
}

and making an async function to handle the streams:

#[tokio::test]
async fn test_server(){
    let main_server = Server::new("127.0.0.1:80");
    let _ = main_server.serve(stream_handler).await;
}

async fn stream_handler(stream: &mut TcpStream){
    println!("new stream!");
}

i suddenly got an error:
error[E0308]: mismatched types
--> src\main.rs:14:13
|
14 | let _ = main_server.serve(stream_handler).await;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected opaque type impl for<'a> Future<Output = ()>
found opaque type impl Future<Output = ()>
= help: consider awaiting on both Futures
= note: distinct uses of impl Trait result in different opaque types

i thought async fn meant that the function returns Future<Output=T>, so i am very confused about what's for<'a>. can anyone explain to me what does for<'a> and why does it appear in this situation?

The issue is in the way async and lifetimes interact. When you write async fn handler(stream: &mut TcpStream) -> (), this turns into a function that looks like fn handler<'s>(stream: &'s mut TcpStream) -> (impl Future<Output=()> + 's). But your Server::serve is asking for a function which looks like fn handler(stream: &mut TcpStream) -> impl Future<Output=()> instead.

There's unfortunately no good way to ask for the prior signature instead... yet. With the ability to use async fn in traits stabilizing in 1.75, it's starting to become more practical.

ah, i tested around and i think i understand, but is there any way to solve this without introducing lifetimes? i am making the sever with a group of friends, and they are very new to rust, and i don't want to make the code too complicated for them, and even just adding lifetimes makes the good look way more complicated to them

(btw i mean the handler function, since my friends probably wouldn't deal with the sever, it does contain lifetimes, so if it's possible to fix this error by only changing the serve() function, not the handler function)

Behold: glorious hacks:

trait HandleStream {
    async fn handle_stream(self, stream: &mut TcpStream);
}

impl<F> HandleStream for F
where
    F: for<'a> FnHack<&'a mut TcpStream>,
    for<'a> <F as FnHack<&'a mut TcpStream>>::OutputHack: Future<Output = ()>,
{
    async fn handle_stream(self, stream: &mut TcpStream) {
        self(stream).await
    }
}

trait FnHack<A>: FnOnce(A) -> Self::OutputHack {
    type OutputHack;
}

impl<F, A, R> FnHack<A> for F
where
    F: FnOnce(A) -> R,
{
    type OutputHack = R;
}

impl Server {
    async fn serve<H: HandleStream>(self, handle_stream: H) -> i32 {
        let mut stream: TcpStream = todo!();
        handle_stream.handle_stream(&mut stream).await;
        0
    }
}

struct Server;

fn main() {
    Server.serve(handle_stream);
}

async fn handle_stream(stream: &mut TcpStream) {
    todo!()
}

There might be a cleaner way to do this. There's probably a cleaner way to do this. I'm kind of sorry I did this. It'd be possible but more ugly without async fn in trait: make a GAT and assign the OutputHack projection to it. At least if you don't look under the covers of the trait to how it's implemented it looks okay.

EDIT: a bit less ugly

Because apparently I'm that easy to nerd snipe. [playground]

trait HandleStream {
    async fn handle_stream(self, stream: &mut TcpStream);
}

impl<F> HandleStream for F
where
    F: for<'a> AsyncFn<&'a mut TcpStream, Output = ()>,
{
    async fn handle_stream(self, stream: &mut TcpStream) {
        self(stream).await
    }
}

trait AsyncFn<A>: FnOnce(A) -> <Self as AsyncFn<A>>::IntoFuture {
    type Output;
    type IntoFuture: Future<Output = <Self as AsyncFn<A>>::Output>;
}

impl<F, A, R> AsyncFn<A> for F
where
    F: FnOnce(A) -> R,
    R: Future,
{
    type Output = <R as Future>::Output;
    type IntoFuture = R;
}

impl Server {
    async fn serve<H: HandleStream>(self, handle_stream: H) -> i32 {
        let mut stream: TcpStream = todo!();
        handle_stream.handle_stream(&mut stream).await;
        0
    }
}

struct Server;

fn main() {
    Server.serve(handle_stream);
}

async fn handle_stream(stream: &mut TcpStream) {
    todo!()
}
3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.