We’ve discovered an important fact.
The “normative” use case of async/await expects that all futures are Send. The default API for executors allows the executor to assume that it can move the future between threads, and so it bounds the future as Future + Send. Exceptions, like embedded programming, don’t use that default API.
What this means is that the vast majority of async fns will have to produce Send futures. For free async functions, this isn’t a problem: every time you call one, we have the concrete type available, and so we can typecheck that it is Send. But for trait methods called on generics, we don’t know. That’s the whole motivation of bounding async fns in the first place.
That is to say, with the original proposal, pretty much every trait would write its async methods async(Send) fn. This seems bad: if 95% of use cases are going to go one way, that way should probably be the default.
But if we decided that async fns return impl Future + Send by default, we need a way to opt out. @aturon proposes that they would just not use the sugar, and instead write the impl Future version:
trait Foo {
// no Send bound
fn foo(&self) -> impl Future<Output = i32> + 'all {
async {
// body
}
}
}
An alternative would be to support a ?Send bound in this position:
trait Foo {
async(?Send) fn foo(&self) -> i32 {
// body
}
}
@Nemo157 I’m especially interested in hearing your thoughts about this because I think in your embedded use case you’re using a single threaded executor. Are you taking advantage of it with non-Send futures?