Axum: inner future of handler is not `Send`

Take this basic server with Axum: https://www.rustexplorer.com/b/od9t00

I have one handler called handler(), it simple returns a String "Hello World". Inside this function there are calls to two asynchronous functions, though their results are disregarded. The functions are broken_future() and working_future(), they have the same signature with the return type Result<(), Box<dyn Error>>.
As the program in its current state everything is working, the program runs and the client gets the response from the server. However, when commenting in the call to broken_future() (in line 39) the compilation fails with the error "future returned by handler is not Send".

  1. My first question is why this line influences the return type, even though it's not part of the actual return type? Of course I could add + Send to the dyn Error return type of broken_future() which would allow the program to compile again. -> https://www.rustexplorer.com/b/rby60u
  2. So my second question is why adding Send is not necessary for working_future() even though it has the same signature as broken_future()?

I could run it in the rustexplorer link that you provided. Are you sure this is reproducing your problem?

@moy2010 Apologies, it seems that I saved it with my workaround solution already applied. I fixed the link in the original post.

I think it all boils down to this auto trait implementation on the TryJoinAll trait:

impl<F> Send for TryJoinAll<F>
where
    F: Send,
    <F as TryFuture>::Error: Send,
    <F as TryFuture>::Ok: Send,

Which will implement Send when F implements it as well.

Now, back to your broken_future implementation, you wrote it like this:

async fn broken_future() -> Result<(), Box<dyn Error>> {
    let futures: Vec<Ready<Result<(), _>>> = vec![];
    let result: Result<Vec<()>, _> = try_join_all(futures).await;
    result.map(|_| ())
}

All those underscores for the Err variant on the Result type are just placeholders for type inference. You are pretty much telling the compiler: "Fill this type for me", and so it will, filling those placeholders with the Errfrom the return type of the function. So, to the compiler it looks like this after type inference:

async fn broken_future() -> Result<(), Box<dyn Error>> {
    let futures: Vec<Ready<Result<(), Box<dyn Error>>>> = vec![];
    let result: Result<Vec<()>, Box<dyn Error>> = try_join_all(futures).await;
    result.map(|_| ())
}

If you then run your code with this version of broken_future, you get a more helpful error from the compiler:

let futures: Vec<Ready<Result<(), Box<dyn Error>>>> = vec![];
         ------- has type `Vec<std::future::Ready<Result<(), Box<dyn std::error::Error>>>>` which is not `Send`
     let result: Result<Vec<()>, Box<dyn Error>> = try_join_all(futures).await;
                                                                        ^^^^^^ await occurs here, with `futures` maybe used later
     result.map(|_| ())

We can then now make it happy by putting the necessary trait bound in those places:

async fn broken_future() -> Result<(), Box<dyn Error>> {
    let futures: Vec<Ready<Result<(), Box<dyn Error + Send>>>> = vec![];
    let result: Result<Vec<()>, Box<dyn Error + Send>> = try_join_all(futures).await;
    result.map(|_| ());

    Ok(())
}

You can see that I even left the signature of broken_future untouched, since we have identified and fixed the breaking parts.

I think that answers your first question. As for the second one:

  1. So my second question is why adding Send is not necessary for working_future() even though it has the same signature as broken_future()?

There's another auto trait implementation, for the Send trait this time which is implemented for Result when both the Ok and the Err variants implement Send as well:

impl<T, E> Send for Result<T, E>
where
    E: Send,
    T: Send,

This is why both working_future and my fixed broken_future work, since they fulfill the required Send bound from Axum's handler.

2 Likes

Thanks a lot for the reply! I ended up using the concrete Error return type in the handler, because I didn't need the boxed type.

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.