Hey folks,
I currently have an understanding problem regarding the new async fn feature in traits. I hope somebody can help.
Here the problem is that the result of check()
might be a future which is not Send
, but why might it be not Send
? When something which implements HealthCheck
is Send
shouldn't this imply that everything in it is Send
? Why should it be explicitly necessary to mention that the result of check()
is Send
?
Regards
keks
As described in the article, when an async fn accesses non-Send
data, the future that impls the async fn is itself !Send
. I think the subtlety here is that Rust never automatically assumes that any Future is Send
; it's just a normal trait, and all of the usual trait constraint rules apply. Namely if your code requires a Send
constraint, it must be added explicitly. The crux of the issue is that it isn't easy to add such a constraint right now (also as described in the article).
Unfortunately not. There are various reasons which I don't have the depth of familiarity to describe. But intuitively, just because a trait is implemented on a type does not mean that it will automatically be required by receivers of the type. The "receiver" in this case is the do_health_check_par
async fn.
For the same reason that you cannot call trait methods without declaring the trait bound/constraint. Take for instance:
// Make sure the `Add` trait is in scope so we can use it
// NB: This is not actually used.
use std::ops::Add;
pub fn add_two_numbers<T>(a: T, b: T) -> T
// TODO: Uncomment the following line to fix the compile error
//where T: Add<Output = T>,
{
a + b
}
The error message in this case is pretty clear that using the +
operator requires a trait bound on Add
, which is in scope (but unused). In this case, the "receiver" of type T
doesn't know that T
implements Add
. Also notice that there are no callers of add_two_numbers()
in this example. The compiler is telling you that the function itself requires type system knowledge (provided in the function declaration) to use the +
operator.
This all might seem rather obvious, but now compare this example back to the async fn in the article. One of the ideas presented is to add a trait bound on Send
in the same way that we can add a bound on Add
here to fix our error. But in that case, there is no syntax available to add the Send
bound to the trait method on the function declaration.
1 Like
Thanks for your respone! 
You're right: When you implement Send
for a type that doesn't mean that everything the type contains has to implement Send
also. I've seen eg here that you can even force the parts which are !Send
to stay in the current thread with Tokio.
Then it makes sense to me that you have to explicitly implement Send
for the return value of check()
! 
It isn't an explicit implementation, though, it's a constraint. It's calling out the requirement in the other direction; "this type must implement Send
", rather than saying "this type implements Send
". That's why I used the term "receiver" (even if it isn't commonly used in this case). I wanted it to be clear that you are not implementing anything by adding these constraints. You are just adding a requirement for the types that you can accept.