Yup, it would be a breaking change, but worth it in my opinion. That's an extremely rare use-case. Again, was planning on writing something up more formally, but this syntax would make async fn
s in traits very straightforward, as well as allow for other things, like a nicer streaming syntax:
async fn foo() -> impl Stream<Output = usize> {
for i in 0..100 {
tokio::sleep(1).await;
yield i;
}
}
Or returning a boxed future, even linting when the compiler detects it would be better to:
async fn foo() -> BoxFuture<'static, usize> {
// ...
1
}
And it would be straightforward to return a named future, instead of an opaque one:
type Sleep = impl Future<Output = usize>;
async fn sleep() -> Sleep {
tokio::sleep(1).await;
}
struct Foo {
sleep: Sleep
}
Of course, these are all possible today, but is more involved. We would have to come up with a new lifetime 'input
/'bikeshed
that covers the lifetime of all input parameters:
async fn foo(&self, bar: &str, baz: &str) -> impl Future<Output = ()> + 'input {
print(&self.bar);
print(bar);
print(baz);
}
If you wanted to write something like that with a boxed future today, you have to write all the lifetimes out explicitly, which isn't very nice. That's the main issue that was brought up in the RFC, and I think adding a 'input
lifetime is a much better solution than hiding the output future type entirely, as it's lead to issues like the one we face now with bounding async trait, and it leads to beginner confusing as async fn
doesn't tell you anything about lifetimes.