Earlier quoted context omitted.
I tried to lean pretty hard into "this syntax is just like threads" in that internals.r-l.org post, when I wrote it, proposing almost exactly what you describe here. Unfortunately problem #1 is not a result of confusion or unnecessary conflation, but a fundamental question of lifetimes- the exact same problem already exists with normal OS threads just as it would with lightweight threads. That is, a function is alway…
I still don't understand why #1 is a problem. > But as I described, this means callers have to add or remove an extra `.run()`/`.await()`/etc. if the API ever switches between the two. Switches between what though? When you want to do something asynchronously, you indeed build a future and later .await() it. Suppose you then want to build that future in a different way, for example by transforming future { foo(x) } t…
Here's the problem in terms of normal OS threads:
fn f(r: &'a i32) -> i32 { ... *r ... }
// oh no, I can't do this:
let i = 42;
thread::spawn(|| f(&i));
Here's the workaround: fn f(r: &'a i32) -> impl FnOnce() -> i32 {
let i = *r;
|| ... i ...
}
// now I can do this:
let i = 42;
thread::spawn(f(&i));
In this case, and the analogous lightweight threads case you're describing, and the "implicit await" post I originally linked, the workaround forces the caller to change its syntax. From `|| f(&i)` to `f(&i)`, or from `async { f(&i) }` to `f(&i)`, or from `future { f(&i) }` to `f(&i)`.But in async/await as currently proposed and implemented, the transformation goes from this...
async fn f(r: &'a i32) -> i32 { ... *r ... }
// oh no, I can't do this:
let i = 42;
task::spawn(f(&i));
...to this: fn f(r: &'a i32) -> impl Future {
let i = *r;
async { ... i ... }
}
// now I can do this:
let i = 42;
thread::spawn(f(&i));
You can imagine someone originally writing the first version, when all their callers just immediately `await` so it's okay if the reference sticks around. But then another caller wants to write something like the above, so they make the transformation above.Under today's futures, all the other call sites keep working (`f(&i).await`) and the new use case starts working. Under our proposals, that transformation would break everyone just using the `f(&i)` syntax, so it probably wouldn't happen, and instead the new caller would have to write this:
thread::spawn(async move {
// move `i` in here, or worse, stuff it in an Arc, even though it's only needed for setup!
let my_i = i;
f(&my_i)
});