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 Err
from 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:
- 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.