3 ms·
Switches between keeping the args for the function's full duration, or returning a closure (async or not) that doesn't hold onto them. Here's the problem in te
by Rusky 7y ago
Switches between keeping the args for the function's full duration, or returning a closure (async or not) that doesn't hold onto them.
Here's the problem in terms of normal OS threads:
fn f<'a>(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<'a>(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<'a>(r: &'a i32) -> i32 { ... *r ... }
// oh no, I can't do this:
let i = 42;
task::spawn(f(&i));
...to this:
fn f<'a>(r: &'a i32) -> impl Future<Output = i32> {
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)
});
- jules 7y agoI see. Would that be such a disaster under your proposal? Original code is: async fn f<'a>(r: &'a i32) -> i32 { ... *r ... } Having some callers future{ f(&i) }.await(). Now the new caller comes in, so we add a function f_future: fn f_future<'a>(r: &'a i32) -> impl Future<Output = i32> let i = *r; async { ... i ... } } The new caller uses f_future and the old callers keep using f. To prevent duplication we can factor out ... i ... into a function g(i) and do g(*r) in the async fn f. The other callers can migrate from future{ f(&i) }.await() to f_future(&i).await() over time. It's not as ideal as not having to change the signature at all, but signature changes can be dealt with. Or is this a big problem with OS threads?
- Rusky 7y agoI agree, there's plenty of ways to work around it, and I'd prefer any of them to the syntactic mess we're in now. I'm not the one making the decisions, though. :)