Question about Send and futures

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! :slight_smile:

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()! :slight_smile:

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.