From 437b691f852a457454e2f0d6320a1b7585ec1d39 Mon Sep 17 00:00:00 2001 From: David Laban Date: Fri, 26 Mar 2021 17:29:06 +0000 Subject: [PATCH 01/14] copy template status_quo story --- .../status_quo/alan_writes_a_web_framework.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/vision/status_quo/alan_writes_a_web_framework.md diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md new file mode 100644 index 00000000..b12a9ca5 --- /dev/null +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -0,0 +1,50 @@ +# 😱 Status quo stories: Template + +*This is a template for adding new "status quo" stories. To propose a new status quo PR, do the following:* + +* *Create a new file in the [`status_quo`] directory named something like `Alan_tries_to_foo.md` or `Grace_does_bar.md`, and start from [the raw source from this template]. You can replace all the italicized stuff. :)* +* *Do not add a link to your story to the [`SUMMARY.md`] file; we'll do it after merging, otherwise there will be too many conflicts.* + +*For more detailed instructions, see the [How To Vision: Status Quo] page!* + +*If you're looking for ideas of what to write about, take a look at the [open issues]. You can also [open an issue of your own] to throw out an idea for others.* + +[How To Vision: Status Quo]: ../how_to_vision/status_quo.md +[the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async-foundations/master/src/vision/status_quo/template.md +[`status_quo`]: https://github.com/rust-lang/wg-async-foundations/tree/master/src/vision/status_quo +[`SUMMARY.md`]: https://github.com/rust-lang/wg-async-foundations/blob/master/src/SUMMARY.md +[open issues]: https://github.com/rust-lang/wg-async-foundations/issues?q=is%3Aopen+is%3Aissue+label%3Astatus-quo-story-ideas +[open an issue of your own]: https://github.com/rust-lang/wg-async-foundations/issues/new?assignees=&labels=good+first+issue%2C+help+wanted%2C+status-quo-story-ideas&template=-status-quo--story-issue.md&title= + + +## 🚧 Warning: Draft status 🚧 + +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. + +If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! + +## The story + +*Write your story here! Feel free to add subsections, citations, links, code examples, whatever you think is best.* + +## 🤔 Frequently Asked Questions + +*Here are some standard FAQ to get you started. Feel free to add more!* + +* **What are the morals of the story?** + * *Talk about the major takeaways-- what do you see as the biggest problems.* +* **What are the sources for this story?** + * *Talk about what the story is based on, ideally with links to blog posts, tweets, or other evidence.* +* **Why did you choose *NAME* to tell this story?** + * *Talk about the character you used for the story and why.* +* **How would this story have played out differently for the other characters?** + * *In some cases, there are problems that only occur for people from specific backgrounds, or which play out differently. This question can be used to highlight that.* + +[character]: ../characters.md +[status quo stories]: ./status_quo.md +[Alan]: ../characters/alan.md +[Grace]: ../characters/grace.md +[Niklaus]: ../characters/niklaus.md +[Barbara]: ../characters/barbara.md +[htvsq]: ../how_to_vision/status_quo.md +[cannot be wrong]: ../how_to_vision/comment.md#comment-to-understand-or-improve-not-to-negate-or-dissuade From eec60ae824320bb11cb8dbfa42998b34a4c0d2d3 Mon Sep 17 00:00:00 2001 From: David Laban Date: Fri, 26 Mar 2021 20:42:55 +0000 Subject: [PATCH 02/14] Alan writes a web framework --- .../status_quo/alan_writes_a_web_framework.md | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index b12a9ca5..18844631 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -19,7 +19,7 @@ ## 🚧 Warning: Draft status 🚧 -This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. +This is a draft "status quo" story submitted as part of the brainstorming period. It is derived from real-life experiences of actual Rust users and is meant to reflect some of the challenges that Async Rust programmers face today. If you would like to expand on this story, or adjust the answers to the FAQ, feel free to open a PR making edits (but keep in mind that, as they reflect peoples' experiences, status quo stories [cannot be wrong], only inaccurate). Alternatively, you may wish to [add your own status quo story][htvsq]! @@ -29,16 +29,28 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee ## 🤔 Frequently Asked Questions -*Here are some standard FAQ to get you started. Feel free to add more!* +[YouBuy](../projects/YouBuy.md) is written using an async web framework that predates the stabilization of async function syntax. When Alan joins the company, it is using async functions for its business logic, but can't use them for request handlers because the framework doesn't support it yet. It requires the handler's return value to be `Box>`. Rather than switching YouBuy to a different web framework, Alan decides to contribute to the web framework himself. + +After a bit of a slog, he manages to make the web framework capable of using an `async fn` as an http request handler. He does this by making a wrapper function that boxes up the `impl Future`. + +It's still not fantastically ergonomic though. Because the web framework predates async function syntax, it requires 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 means that Alan can't use the `?` operator inside his http request handlers. Alan knows answer is to make another wrapper function so that the handler can take an `&mut` reference to `State` for the lifetime of the future, but Alan can't work out how to express this. He submits his pull-request upstream as-is, but it nags on his mind that he has been defeated. + +Shortly afterwards, someone raises a bug about `?`, and a few other web framework contributors try to get it to work, but they also get stuck. When Alan tries it, the compiler diagnostics keep sending him around in circles. He can work out how to express the lifetimes for a function that returns a `Box` but not an `impl Future` because of how where clauses are expressed. Alan longs to be able to say "this function takes an async function as a callback" (`fn register_handler(handler: impl async Fn(state: &mut State) -> Result)`) and have Rust elide the lifetimes for him, like how they are elided for async functions. + +A month later, one of the contributors finds a forum comment by Barbara explaining how to express what the Alan is after (using higher-order lifetimes and a helper trait). They implement this and merge it. + +When Alan sees another open source project struggling with the same issue, he notices that Barbara has helped them out as well. + * **What are the morals of the story?** - * *Talk about the major takeaways-- what do you see as the biggest problems.* + * Callback-based APIs with async callbacks are a bit fiddly, because of the `impl Future` return type, but not insurmountable. + * Callback-based APIs with async callbacks that borrow their arguments are almost impossible to write without help. * **What are the sources for this story?** - * *Talk about what the story is based on, ideally with links to blog posts, tweets, or other evidence.* -* **Why did you choose *NAME* to tell this story?** - * *Talk about the character you used for the story and why.* + * This is from the author's [own experience](https://github.com/rust-lang/wg-async-foundations/issues/78#issuecomment-808193936). +* **Why did you choose Alan/YouBuy to tell this story?** + * Callback-based apis are a super-common way to interact with web frameworks. I'm not sure how common they are in other fields. * **How would this story have played out differently for the other characters?** - * *In some cases, there are problems that only occur for people from specific backgrounds, or which play out differently. This question can be used to highlight that.* + * I suspect that even many Barbara-shaped developers would struggle with this problem. [character]: ../characters.md [status quo stories]: ./status_quo.md From 4ed45590e8b39d0441da3ebd166f2a0c08fa73e3 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 31 Mar 2021 11:01:11 +0100 Subject: [PATCH 03/14] delete the italicized text --- .../status_quo/alan_writes_a_web_framework.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index 18844631..9b4c869e 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -1,13 +1,5 @@ # 😱 Status quo stories: Template -*This is a template for adding new "status quo" stories. To propose a new status quo PR, do the following:* - -* *Create a new file in the [`status_quo`] directory named something like `Alan_tries_to_foo.md` or `Grace_does_bar.md`, and start from [the raw source from this template]. You can replace all the italicized stuff. :)* -* *Do not add a link to your story to the [`SUMMARY.md`] file; we'll do it after merging, otherwise there will be too many conflicts.* - -*For more detailed instructions, see the [How To Vision: Status Quo] page!* - -*If you're looking for ideas of what to write about, take a look at the [open issues]. You can also [open an issue of your own] to throw out an idea for others.* [How To Vision: Status Quo]: ../how_to_vision/status_quo.md [the raw source from this template]: https://raw.githubusercontent.com/rust-lang/wg-async-foundations/master/src/vision/status_quo/template.md @@ -25,10 +17,6 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee ## The story -*Write your story here! Feel free to add subsections, citations, links, code examples, whatever you think is best.* - -## 🤔 Frequently Asked Questions - [YouBuy](../projects/YouBuy.md) is written using an async web framework that predates the stabilization of async function syntax. When Alan joins the company, it is using async functions for its business logic, but can't use them for request handlers because the framework doesn't support it yet. It requires the handler's return value to be `Box>`. Rather than switching YouBuy to a different web framework, Alan decides to contribute to the web framework himself. After a bit of a slog, he manages to make the web framework capable of using an `async fn` as an http request handler. He does this by making a wrapper function that boxes up the `impl Future`. @@ -41,6 +29,7 @@ A month later, one of the contributors finds a forum comment by Barbara explaini When Alan sees another open source project struggling with the same issue, he notices that Barbara has helped them out as well. +## 🤔 Frequently Asked Questions * **What are the morals of the story?** * Callback-based APIs with async callbacks are a bit fiddly, because of the `impl Future` return type, but not insurmountable. From aa58365bd392b4ced2afdc79674dc41b279c67c5 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 31 Mar 2021 11:51:22 +0100 Subject: [PATCH 04/14] add examples down to `async fn` --- .../status_quo/alan_writes_a_web_framework.md | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index 9b4c869e..00c04546 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -17,11 +17,67 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee ## The story -[YouBuy](../projects/YouBuy.md) is written using an async web framework that predates the stabilization of async function syntax. When Alan joins the company, it is using async functions for its business logic, but can't use them for request handlers because the framework doesn't support it yet. It requires the handler's return value to be `Box>`. Rather than switching YouBuy to a different web framework, Alan decides to contribute to the web framework himself. - -After a bit of a slog, he manages to make the web framework capable of using an `async fn` as an http request handler. He does this by making a wrapper function that boxes up the `impl Future`. - -It's still not fantastically ergonomic though. Because the web framework predates async function syntax, it requires 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 means that Alan can't use the `?` operator inside his http request handlers. Alan knows answer is to make another wrapper function so that the handler can take an `&mut` reference to `State` for the lifetime of the future, but Alan can't work out how to express this. He submits his pull-request upstream as-is, but it nags on his mind that he has been defeated. +[YouBuy](../projects/YouBuy.md) is written using an async web framework that predates the stabilization of async function syntax. When [Alan] joins the company, it is using async functions for its business logic, but can't use them for request handlers because the framework doesn't support it yet. It requires the handler's return value to be `Box>`. Because the web framework predates async function syntax, it requires you to take ownership of the request context (`State`) and return it alongside your response in the success/error cases. This means that even with async syntax, an http route handler in this web framework looks something like this (from [the Gotham Diesel example](https://github.com/gotham-rs/gotham/blob/9f10935bf28d67339c85f16418736a4b6e1bd36e/examples/diesel/src/main.rs)): + +```rust +fn get_products_handler(state: State) -> Pin> { + use crate::schema::products::dsl::*; + + async move { + let repo = Repo::borrow_from(&state); + let result = repo.run(move |conn| products.load::(&conn)).await; + match result { + Ok(users) => { + let body = serde_json::to_string(&users).expect("Failed to serialize users."); + let res = create_response(&state, StatusCode::OK, mime::APPLICATION_JSON, body); + Ok((state, res)) + } + Err(e) => Err((state, e.into())), + } + } + .boxed() +} +``` +and then it is registered like this: +```rust + router_builder.get("/").to(get_products_handler); +``` + +The handler code is forced to drift to the right a lot, because of the async block, and the lack of ability to use `?` forces the use of a match block, which drifts even further to the right. + +Rather than switching YouBuy to a different web framework, Alan decides to contribute to the web framework himself. After a bit of a slog and a bit of where-clause-soup, he manages to make the web framework capable of using an `async fn` as an http request handler. He does this by extending the router builder with a closure that boxes up the `impl Future` from the async fn and then passes that closure on to `.to()`. + +```rust + fn to_async(self, handler: H) + where + Self: Sized, + H: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static, + Fut: Future + Send + 'static, + { + self.to(move |s: State| handler(s).boxed()) + } +``` + +This allows him to strip out the async blocks in his handlers and use `async fn` instead. + +```rust +async fn get_products_handler(state: State) -> HandlerResult { + use crate::schema::products::dsl::*; + + let repo = Repo::borrow_from(&state); + let result = repo.run(move |conn| products.load::(&conn)).await; + match result { + Ok(users) => { + let body = serde_json::to_string(&users).expect("Failed to serialize users."); + let res = create_response(&state, StatusCode::OK, mime::APPLICATION_JSON, body); + Ok((state, res)) + } + Err(e) => Err((state, e.into())), + } +} +``` + +It's still not fantastically ergonomic though. [[Because the web framework predates async function syntax, it requires 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 means that Alan can't use the `?` operator inside his http request handlers. Alan knows answer is to make another wrapper function so that the handler can take an `&mut` reference to `State` for the lifetime of the future, but Alan can't work out how to express this. He submits his pull-request upstream as-is, but it nags on his mind that he has been defeated. Shortly afterwards, someone raises a bug about `?`, and a few other web framework contributors try to get it to work, but they also get stuck. When Alan tries it, the compiler diagnostics keep sending him around in circles. He can work out how to express the lifetimes for a function that returns a `Box` but not an `impl Future` because of how where clauses are expressed. Alan longs to be able to say "this function takes an async function as a callback" (`fn register_handler(handler: impl async Fn(state: &mut State) -> Result)`) and have Rust elide the lifetimes for him, like how they are elided for async functions. From 11145d3b831b465e00ad55907d866ec4b90260a4 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 31 Mar 2021 12:13:39 +0100 Subject: [PATCH 05/14] Rename users to prods, since this example is about products ("It was like this when I got here") --- src/vision/status_quo/alan_writes_a_web_framework.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index 00c04546..05239558 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -27,8 +27,8 @@ fn get_products_handler(state: State) -> Pin> { let repo = Repo::borrow_from(&state); let result = repo.run(move |conn| products.load::(&conn)).await; match result { - Ok(users) => { - let body = serde_json::to_string(&users).expect("Failed to serialize users."); + Ok(prods) => { + let body = serde_json::to_string(&prods).expect("Failed to serialize prods."); let res = create_response(&state, StatusCode::OK, mime::APPLICATION_JSON, body); Ok((state, res)) } @@ -67,8 +67,8 @@ async fn get_products_handler(state: State) -> HandlerResult { let repo = Repo::borrow_from(&state); let result = repo.run(move |conn| products.load::(&conn)).await; match result { - Ok(users) => { - let body = serde_json::to_string(&users).expect("Failed to serialize users."); + Ok(prods) => { + let body = serde_json::to_string(&prods).expect("Failed to serialize prods."); let res = create_response(&state, StatusCode::OK, mime::APPLICATION_JSON, body); Ok((state, res)) } From 7b5ba0cb4dc242631208a2ac772b4338c3d1c449 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 31 Mar 2021 12:14:49 +0100 Subject: [PATCH 06/14] motivate why drifting to the right is bad, from a go style guide --- src/vision/status_quo/alan_writes_a_web_framework.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index 05239558..b4475108 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -43,7 +43,7 @@ and then it is registered like this: router_builder.get("/").to(get_products_handler); ``` -The handler code is forced to drift to the right a lot, because of the async block, and the lack of ability to use `?` forces the use of a match block, which drifts even further to the right. +The handler code is forced to drift to the right a lot, because of the async block, and the lack of ability to use `?` forces the use of a match block, which drifts even further to the right. This goes against [what he has learned from his days writing go](https://github.com/uber-go/guide/blob/master/style.md#reduce-nesting). Rather than switching YouBuy to a different web framework, Alan decides to contribute to the web framework himself. After a bit of a slog and a bit of where-clause-soup, he manages to make the web framework capable of using an `async fn` as an http request handler. He does this by extending the router builder with a closure that boxes up the `impl Future` from the async fn and then passes that closure on to `.to()`. From 32607e20523f6d1ab2a56802b4dfce9b34f1230b Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 31 Mar 2021 12:15:28 +0100 Subject: [PATCH 07/14] Add more context to examples, to make them clearer --- src/vision/status_quo/alan_writes_a_web_framework.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index b4475108..d47df1e5 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -20,6 +20,10 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee [YouBuy](../projects/YouBuy.md) is written using an async web framework that predates the stabilization of async function syntax. When [Alan] joins the company, it is using async functions for its business logic, but can't use them for request handlers because the framework doesn't support it yet. It requires the handler's return value to be `Box>`. Because the web framework predates async function syntax, it requires you to take ownership of the request context (`State`) and return it alongside your response in the success/error cases. This means that even with async syntax, an http route handler in this web framework looks something like this (from [the Gotham Diesel example](https://github.com/gotham-rs/gotham/blob/9f10935bf28d67339c85f16418736a4b6e1bd36e/examples/diesel/src/main.rs)): ```rust +// For reference, the framework defines these type aliases. +pub type HandlerResult = Result<(State, Response), (State, HandlerError)>; +pub type HandlerFuture = dyn Future + Send; + fn get_products_handler(state: State) -> Pin> { use crate::schema::products::dsl::*; @@ -57,10 +61,17 @@ Rather than switching YouBuy to a different web framework, Alan decides to contr self.to(move |s: State| handler(s).boxed()) } ``` +The handler registration then becomes: +```rust + router_builder.get("/").to_async(get_products_handler); +``` This allows him to strip out the async blocks in his handlers and use `async fn` instead. ```rust +// Type the library again, in case you've forgotten: +pub type HandlerResult = Result<(State, Response), (State, HandlerError)>; + async fn get_products_handler(state: State) -> HandlerResult { use crate::schema::products::dsl::*; From 97a681b3745860221f0d28c45972f7560098c778 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 31 Mar 2021 12:28:00 +0100 Subject: [PATCH 08/14] describe the promised land with an example --- .../status_quo/alan_writes_a_web_framework.md | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index d47df1e5..17faca9b 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -88,7 +88,54 @@ async fn get_products_handler(state: State) -> HandlerResult { } ``` -It's still not fantastically ergonomic though. [[Because the web framework predates async function syntax, it requires 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 means that Alan can't use the `?` operator inside his http request handlers. Alan knows answer is to make another wrapper function so that the handler can take an `&mut` reference to `State` for the lifetime of the future, but Alan can't work out how to express this. He submits his pull-request upstream as-is, but it nags on his mind that he has been defeated. +It's still not fantastically ergonomic though. Because the handler takes ownership of State and returns it in tuples in the result, Alan can't use the `?` operator inside his http request handlers. If he tries use `?` in a handler, like this: + +```rust +async fn get_products_handler(state: State) -> HandlerResult { + use crate::schema::products::dsl::*; + + let repo = Repo::borrow_from(&state); + let prods = repo + .run(move |conn| products.load::(&conn)) + .await?; + let body = serde_json::to_string(&prods).expect("Failed to serialize prods."); + let res = create_response(&state, StatusCode::OK, mime::APPLICATION_JSON, body); + Ok((state, res)) +} +``` +then he receives: +``` +error[E0277]: `?` couldn't convert the error to `(gotham::state::State, HandlerError)` + --> examples/diesel/src/main.rs:84:15 + | +84 | .await?; + | ^ the trait `From` is not implemented for `(gotham::state::State, HandlerError)` + | + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + = note: required by `std::convert::From::from` +``` + +Alan knows that the answer is to make another wrapper function, so that the handler can take an `&mut` reference to `State` for the lifetime of the future, like this: + +```rust +async fn get_products_handler(state: &mut State) -> Result, HandlerError> { + use crate::schema::products::dsl::*; + + let repo = Repo::borrow_from(&state); + let prods = repo + .run(move |conn| products.load::(&conn)) + .await?; + let body = serde_json::to_string(&prods).expect("Failed to serialize prods."); + let res = create_response(&state, StatusCode::OK, mime::APPLICATION_JSON, body); + Ok(res) +} +``` +and then register it with: +```rust + route.get("/").to_async_borrowing(get_products_handler); +``` + +but Alan can't work out how to express the type signature for the `.to_async_borrowing()` helper function. He submits his `.to_async()` pull-request upstream as-is, but it nags on his mind that he has been defeated. Shortly afterwards, someone raises a bug about `?`, and a few other web framework contributors try to get it to work, but they also get stuck. When Alan tries it, the compiler diagnostics keep sending him around in circles. He can work out how to express the lifetimes for a function that returns a `Box` but not an `impl Future` because of how where clauses are expressed. Alan longs to be able to say "this function takes an async function as a callback" (`fn register_handler(handler: impl async Fn(state: &mut State) -> Result)`) and have Rust elide the lifetimes for him, like how they are elided for async functions. From 5d9f6a4cf0f1abff9767a20a7236b985b22436c5 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 31 Mar 2021 12:38:02 +0100 Subject: [PATCH 09/14] address/add TODOs for review comments/outstanding bits --- src/vision/status_quo/alan_writes_a_web_framework.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index 17faca9b..a2288062 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -137,16 +137,16 @@ and then register it with: but Alan can't work out how to express the type signature for the `.to_async_borrowing()` helper function. He submits his `.to_async()` pull-request upstream as-is, but it nags on his mind that he has been defeated. -Shortly afterwards, someone raises a bug about `?`, and a few other web framework contributors try to get it to work, but they also get stuck. When Alan tries it, the compiler diagnostics keep sending him around in circles. He can work out how to express the lifetimes for a function that returns a `Box` but not an `impl Future` because of how where clauses are expressed. Alan longs to be able to say "this function takes an async function as a callback" (`fn register_handler(handler: impl async Fn(state: &mut State) -> Result)`) and have Rust elide the lifetimes for him, like how they are elided for async functions. +Shortly afterwards, someone raises a bug about `?`, and a few other web framework contributors try to get it to work, but they also get stuck. When Alan tries it, the compiler diagnostics keep sending him around in circles . He can work out how to express the lifetimes for a function that returns a `Box` but not an `impl Future` because of how where clauses are expressed. Alan longs to be able to say "this function takes an async function as a callback" (`fn register_handler(handler: impl async Fn(state: &mut State) -> Result)`) and have Rust elide the lifetimes for him, like how they are elided for async functions. -A month later, one of the contributors finds a forum comment by Barbara explaining how to express what the Alan is after (using higher-order lifetimes and a helper trait). They implement this and merge it. +A month later, one of the contributors finds a forum comment by [Barbara] explaining how to express what Alan is after (using higher-order lifetimes and a helper trait). They implement this and merge it . When Alan sees another open source project struggling with the same issue, he notices that Barbara has helped them out as well. ## 🤔 Frequently Asked Questions * **What are the morals of the story?** - * Callback-based APIs with async callbacks are a bit fiddly, because of the `impl Future` return type, but not insurmountable. + * Callback-based APIs with async callbacks are a bit fiddly, because of the `impl Future` return type forcing you to write where-clause-soup, but not insurmountable. * Callback-based APIs with async callbacks that borrow their arguments are almost impossible to write without help. * **What are the sources for this story?** * This is from the author's [own experience](https://github.com/rust-lang/wg-async-foundations/issues/78#issuecomment-808193936). From 2a43e28c79000b3c9aec448b45ffefeb743dcb7a Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 31 Mar 2021 14:56:22 +0100 Subject: [PATCH 10/14] copy-paste gotham's .to_async_borrowing() impl --- .../status_quo/alan_writes_a_web_framework.md | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index a2288062..e994c5fb 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -139,9 +139,66 @@ but Alan can't work out how to express the type signature for the `.to_async_bor Shortly afterwards, someone raises a bug about `?`, and a few other web framework contributors try to get it to work, but they also get stuck. When Alan tries it, the compiler diagnostics keep sending him around in circles . He can work out how to express the lifetimes for a function that returns a `Box` but not an `impl Future` because of how where clauses are expressed. Alan longs to be able to say "this function takes an async function as a callback" (`fn register_handler(handler: impl async Fn(state: &mut State) -> Result)`) and have Rust elide the lifetimes for him, like how they are elided for async functions. -A month later, one of the contributors finds a forum comment by [Barbara] explaining how to express what Alan is after (using higher-order lifetimes and a helper trait). They implement this and merge it . +A month later, one of the contributors finds a forum comment by [Barbara] explaining how to express what Alan is after (using higher-order lifetimes and a helper trait). They implement this and merge it. The final `.to_async_borrowing()` implementation ends up looking like this (also from [Gotham](https://github.com/gotham-rs/gotham/blob/89c491fb4322bbc6fbcc8405c3a33e0634f7cbba/gotham/src/router/builder/single.rs)): -When Alan sees another open source project struggling with the same issue, he notices that Barbara has helped them out as well. +```rust +pub trait AsyncHandlerFn<'a> { + type Res: IntoResponse + 'static; + type Fut: std::future::Future> + Send + 'a; + fn call(self, arg: &'a mut State) -> Self::Fut; +} + +impl<'a, Fut, R, F> AsyncHandlerFn<'a> for F +where + F: FnOnce(&'a mut State) -> Fut, + R: IntoResponse + 'static, + Fut: std::future::Future> + Send + 'a, +{ + type Res = R; + type Fut = Fut; + fn call(self, state: &'a mut State) -> Fut { + self(state) + } +} + +pub trait HandlerMarker { + fn call_and_wrap(self, state: State) -> Pin>; +} + +impl HandlerMarker for F +where + R: IntoResponse + 'static, + for<'a> F: AsyncHandlerFn<'a, Res = R> + Send + 'static, +{ + fn call_and_wrap(self, mut state: State) -> Pin> { + async move { + let fut = self.call(&mut state); + let result = fut.await; + match result { + Ok(data) => { + let response = data.into_response(&state); + Ok((state, response)) + } + Err(err) => Err((state, err)), + } + } + .boxed() + } +} + +... + fn to_async_borrowing(self, handler: F) + where + Self: Sized, + F: HandlerMarker + Copy + Send + Sync + RefUnwindSafe + 'static, + { + self.to(move |state: State| handler.call_and_wrap(state)) + } +``` + +Alan is still not sure whether it can be simplified. + +When Alan sees another open source project struggling with the same issue, he notices that Barbara has helped them out as well. Alan wonders how many people in the community would be able to write `.to_async_borrowing()` without help. ## 🤔 Frequently Asked Questions From f9f42f002fbdf3d010277ad0e35cd2bf73389450 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 5 Apr 2021 11:55:31 -0400 Subject: [PATCH 11/14] Update src/vision/status_quo/alan_writes_a_web_framework.md --- src/vision/status_quo/alan_writes_a_web_framework.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index e994c5fb..9a48ecc9 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -88,7 +88,7 @@ async fn get_products_handler(state: State) -> HandlerResult { } ``` -It's still not fantastically ergonomic though. Because the handler takes ownership of State and returns it in tuples in the result, Alan can't use the `?` operator inside his http request handlers. If he tries use `?` in a handler, like this: +It's still not fantastically ergonomic though. Because the handler takes ownership of State and returns it in tuples in the result, Alan can't use the `?` operator inside his http request handlers. If he tries to use `?` in a handler, like this: ```rust async fn get_products_handler(state: State) -> HandlerResult { From aa58eac121e0ec0650c5f2bf2eac754b527f274e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 5 Apr 2021 11:56:19 -0400 Subject: [PATCH 12/14] tweak to ### headings --- .../status_quo/alan_writes_a_web_framework.md | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index 9a48ecc9..0b6c0665 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -202,15 +202,22 @@ When Alan sees another open source project struggling with the same issue, he no ## 🤔 Frequently Asked Questions -* **What are the morals of the story?** - * Callback-based APIs with async callbacks are a bit fiddly, because of the `impl Future` return type forcing you to write where-clause-soup, but not insurmountable. - * Callback-based APIs with async callbacks that borrow their arguments are almost impossible to write without help. -* **What are the sources for this story?** - * This is from the author's [own experience](https://github.com/rust-lang/wg-async-foundations/issues/78#issuecomment-808193936). -* **Why did you choose Alan/YouBuy to tell this story?** - * Callback-based apis are a super-common way to interact with web frameworks. I'm not sure how common they are in other fields. -* **How would this story have played out differently for the other characters?** - * I suspect that even many Barbara-shaped developers would struggle with this problem. +### What are the morals of the story? + +* Callback-based APIs with async callbacks are a bit fiddly, because of the `impl Future` return type forcing you to write where-clause-soup, but not insurmountable. +* Callback-based APIs with async callbacks that borrow their arguments are almost impossible to write without help. + +### What are the sources for this story? + +* This is from the author's [own experience](https://github.com/rust-lang/wg-async-foundations/issues/78#issuecomment-808193936). + +### Why did you choose Alan/YouBuy to tell this story? + +* Callback-based apis are a super-common way to interact with web frameworks. I'm not sure how common they are in other fields. + +### How would this story have played out differently for the other characters? + +* I suspect that even many Barbara-shaped developers would struggle with this problem. [character]: ../characters.md [status quo stories]: ./status_quo.md From 292743eea03ff19946c60ce13b597b0f9ff75a1b Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 5 Apr 2021 11:57:13 -0400 Subject: [PATCH 13/14] Update alan_writes_a_web_framework.md --- src/vision/status_quo/alan_writes_a_web_framework.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index 0b6c0665..2bce98b6 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -198,6 +198,8 @@ where Alan is still not sure whether it can be simplified. +Later on, other developers on the project attempt to extend this approach to work with closures, but they encounter limitations in rustc that seem to make it not work ([rust-lang/rust#70263](https://github.com/rust-lang/rust/issues/70263)). + When Alan sees another open source project struggling with the same issue, he notices that Barbara has helped them out as well. Alan wonders how many people in the community would be able to write `.to_async_borrowing()` without help. ## 🤔 Frequently Asked Questions From 723a73142c35a2361b75265e3d573d8c6b6012da Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 5 Apr 2021 11:57:42 -0400 Subject: [PATCH 14/14] Update alan_writes_a_web_framework.md --- src/vision/status_quo/alan_writes_a_web_framework.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vision/status_quo/alan_writes_a_web_framework.md b/src/vision/status_quo/alan_writes_a_web_framework.md index 2bce98b6..e43ab360 100644 --- a/src/vision/status_quo/alan_writes_a_web_framework.md +++ b/src/vision/status_quo/alan_writes_a_web_framework.md @@ -1,4 +1,4 @@ -# 😱 Status quo stories: Template +# 😱 Status quo stories: Alan writes a web framework [How To Vision: Status Quo]: ../how_to_vision/status_quo.md