rust-lang / wg-async

Working group dedicated to improving the foundations of Async I/O in Rust
https://rust-lang.github.io/wg-async/
Apache License 2.0
379 stars 88 forks source link

Async-closures or reference-parameterized-closures-returning-futures #78

Open stuhood opened 3 years ago

stuhood commented 3 years ago

Brief summary

Not having async closures can mean needing to resort to macros to create certain (fairly common) types of combinators (ones where a closure that returns a future runs into lifetime issues).

We experienced this in https://github.com/pantsbuild/pants/issues/11548, and I ended up writing a macro to put what would have been a parameter to a closure-returning-a-future or async-closure directly on the stack instead: https://github.com/pantsbuild/pants/pull/11759

Optional details

nikomatsakis commented 3 years ago

@stuhood thanks for the example! I'd love to see a write-up dramatizing and spelling out some of the downsides of this macro approach you ended up with.

alsuren commented 3 years ago

I can provide my own story. It's more of a saga, but contains two examples of this in two different projects. I would consider myself to be an Alan at the time, but it's a tale that involves a few other characters like Alan and Barbara. I will use real names and references, and once it's all written down we can trim it down and extract a persona story out of it.

David was using the gotham web framework for some toy projects since before async/await became stable. He was new to rust, and not using it professionally, but had a friend with experience contributing to the compiler that could help him. Gotham's examples were generally clear and well tested, and David enjoyed contributing to them, to show other people how to use the framework. Once async/await became stable, David contributed some examples of how to use async functions to handle http requests (https://github.com/gotham-rs/gotham/pull/281 - first drafted in October 2018 on nightly, "finished" Feb 2020 and merged June 2020 after a maintainership change. This uncovered some ergonomics problems around boxing futures, so a helper method was added in the same PR, but that's unrelated to this ticket).

Since Gotham's API predates async functions, it historically required you to take ownership of the request context (fn handler(state: State)) and return it alongside your response in the success/error cases (Ok((state, response)) or Err((state, error))). This meant that you couldn't use the ? operator inside your http request handler. The answer was for the handler to take an &mut reference to State for the lifetime of the Future, but David couldn't work out how to express this.

In mid May, someone predictably raised a bug about ? (https://github.com/gotham-rs/gotham/issues/431) and a few other Gotham contributors tried to get it to work but got stuck. When David tried it, the compiler diagnostics kept sending him around in circles. He could work out how to express the lifetimes for a function that returned a Box<dyn Future + 'a> but not an impl Future. David identified https://github.com/rust-lang/rust/issues/45994 as a possible solution to the problem, but secretly longed to be able to say fn register_handler(f: impl async Fn(state: &mut State) -> Result<Response, Error> and have Rust elide the lifetimes for him). David even tried implementing this himself, but he didn't have anyone to help him, and eventually gave up.

Eventually, towards the end of June, someone found a forum reply by Alice explaining how to express what the Gotham team were after (higher order lifetimes and helper traits). This was picked up by the gotham team and merged in https://github.com/gotham-rs/gotham/pull/450 .

In parallel with this, David helped the Goose team with a proof-of-concept port their http load testing tool to use async, https://github.com/tag1consulting/goose/pull/8 but again ran out of steam before completing the work. The Goose team had a couple of shots at this, and problems storing the callback closure which borrowed some context (&User). Eventually, a reddit comment by Alice got them unstuck (higher order lifetimes and helper traits again), and they got it working in a later PR https://github.com/tag1consulting/goose/pull/22 . They could use fns as callbacks, but not closures, and they needed a helper macro to box up the callback fn for ergonomics reasons. Once the Gotham fixes were approved and merged, David produced another proof-of-concept for Goose (https://github.com/tag1consulting/goose/pull/94) which allowed them to use closures as well. This got picked up by the goose team in https://github.com/tag1consulting/goose/pull/120 and merged.

I can write this up as a proper story PR if you want. I guess I'd want to boil the essence down to a single story involving an interaction between Alan and Barbara?

nikomatsakis commented 3 years ago

That's really helpful @alsuren .. I'll take a look at #101 too, haven't gotten to it yet.

UPDATE: Oh, I guess I did.