Skip to content

Small cosmetic changes #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/vision/shiny_future/barbara_makes_a_wish.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Barbara now remembers hearing something about a `wish4-async-insight` crate, whi

She adds the crate as a dependency to her `Cargo.toml`, renaming it to just `insight` to make it easier to reference in her code, and then initializes it in her main async function.

```rust,ignore
```rust
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
insight::init(); // new code
...
Expand All @@ -31,7 +31,7 @@ async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {

Barbara rebuilds and runs her program again. She doesn't see anything different in the terminal output for the program itself though, and the behavior is the same as before: hitting an endpoint, nothing happens. She double-checks the readme for the `wish4-async-insight` crate, and realizes that she needs to connect other programs to her service to observe the insights being gathered. Barbara decides that she wants to customize the port that `insight` is listening on before she starts her experiments with those programs.

```rust,ignore
```rust
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
insight::init(listen_port => 8080); // new code, leveraging keyword arguments feature added in 2024
...
Expand Down
6 changes: 2 additions & 4 deletions src/vision/status_quo/alan_finds_database_drops_hard.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee

Alan has been adding an extension to YouBuy that launches a singleton actor which interacts with a Sqlite database using the `sqlx` crate. The Sqlite database only permits a single active connection at a time, but this is not a problem, because the actor is a singleton, and so there only should be one at a time. He consults the documentation for `sqlx` and comes up with the following code to create a connection and do the query he needs:

```rust,ignore
```rust
use sqlx::Connection;

#[async_std::main]
Expand Down Expand Up @@ -41,7 +41,7 @@ Alan tries to figure out what happened from the logs, but the only information h

He's a bit confused, because he's accustomed to having things generally be cleaned up automatically when they get dropped (for example, dropping a [`File`](https://doc.rust-lang.org/std/fs/struct.File.html) will close it). Searching the docs, he sees the [`close`](https://docs.rs/sqlx/0.5.1/sqlx/trait.Connection.html#tymethod.close) method, but the comments confirm that he shouldn't have to call it explicitly: "This method is not required for safe and consistent operation. However, it is recommended to call it instead of letting a connection drop as the database backend will be faster at cleaning up resources." Still, just in case, he decides to add a call to `close` into his code. It does seem to help some, but he is still able to reproduce the problem if he refreshes often enough. Feeling confused, he adds a log statement right before calling `close` to see if it is working:

```rust,ignore
```rust
use sqlx::Connection;

#[async_std::main]
Expand Down Expand Up @@ -76,8 +76,6 @@ Alan briefly considers rearchitecting his application in more extreme ways to re

## 🤔 Frequently Asked Questions

*Here are some standard FAQ to get you started. Feel free to add more!*

### **What are the morals of the story?**
* Rust's async story is lacking a way of executing async operations in destructors. Spawning is a workaround, but it can have unexpected side-effects.
* The story demonstrates solid research steps that Alan uses to understand and resolve his problem.
Expand Down
16 changes: 8 additions & 8 deletions src/vision/status_quo/alan_hates_writing_a_stream.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ After a couple weeks learning Rust basics, Alan quickly understands `async` and

Eventually, Alan realizes that some responses have enormous bodies, and would like to stream them instead of buffering them fully in memory. He's *used* the `Stream` trait before. Using it was very natural, and followed a similar pattern to regular `async`/`await`:

```rust,ignore
```rust
while let Some(chunk) = body.next().await? {
file.write_all(&chunk).await?;
}
Expand All @@ -26,7 +26,7 @@ However, _implementing_ `Stream` turns out to be rather different. With a quick

Alan first hoped he could simply write signing stream imperatively, reusing his new knowledge of `async` and `await`, and assuming it'd be similar to JavaScript:

```rust,ignore
```rust
async* fn sign(file: ReaderStream) -> Result<Vec<u8>, Error> {
let mut sig = Signature::new();

Expand Down Expand Up @@ -73,7 +73,7 @@ Implementing a `Stream` means writing async code in a way that doesn't _feel_ li

Unsure of what the final code will look like, he starts with:

```rust,ignore
```rust
struct SigningFile;

impl Stream for SigningFile {
Expand Down Expand Up @@ -101,7 +101,7 @@ With `Pin` hopefully ignored, Alan next notices that in the imperative style he

He thinks about his stream's states, and settles on the following structure:

```rust,ignore
```rust
struct SigningFile {
state: State,
file: ReaderStream,
Expand All @@ -118,7 +118,7 @@ enum State {

It turns out it was more complicated than Alan thought (the author made this same mistake). The `digest` method of `Signature` is `async`, _and_ it consumes the signature, so the state machine needs to be adjusted. The signature needs to be able to be moved out, and it needs to be able to store a future from an `async fn`. Trying to figure out how to represent that in the type system was difficult. He considered adding a generic `T: Future` to the `State` enum, but then wasn't sure what to set that generic to. Then, he tries just writing `Signing(impl Future)` as a state variant, but that triggers a compiler error that `impl Trait` isn't allowed outside of function return types. Patient [Barbara] helped again, so that Alan learns to just store a `Pin<Box<dyn Future>>`, wondering if the `Pin` there is important.

```rust,ignore
```rust
struct SigningFile {
state: State,
}
Expand All @@ -132,7 +132,7 @@ enum State {

Now he tries to write the `poll_next` method, checking readiness of individual steps (thankfully, Alan remembers `ready!` from the futures 0.1 blog posts he read) and proceeding to the next state, while grumbling away the weird `Pin` noise:

```rust,ignore
```rust
match self.state {
State::File(ref mut file, ref mut sig) => {
match ready!(Pin::new(file).poll_next(cx)) {
Expand Down Expand Up @@ -166,7 +166,7 @@ Oh well, at least it _works_, right?

So far, Alan hasn't paid too much attention to `Context` and `Poll`. It's been fine to simply pass them along untouched. There's a confusing bug in his state machine. Let's look more closely:

```rust,ignore
```rust
// zooming in!
match ready!(Pin::new(file).poll_next(cx)) {
Some(result) => {
Expand All @@ -188,7 +188,7 @@ The compiler doesn't help at all, and he re-reads his code multiple times, but b

All too often, since we don't want to duplicate code in multiple branches, the solution for Alan is to add an odd `loop` around the whole thing, so that the next match branch uses the `Context`:

```rust,ignore
```rust
loop {
match self.state {
State::File(ref mut file, ref mut sig) => {
Expand Down
16 changes: 8 additions & 8 deletions src/vision/status_quo/alan_lost_the_world.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Alan heard about a project to reimplement a deprecated browser plugin using Rust
3. `await` the `Future` in an `async` function
4. Do whatever they want with the resulting data

```rust,ignore
```rust
use web_sys::{Request, window};

fn make_request(src: &url) -> Request {
Expand All @@ -34,7 +34,7 @@ Alan adds calls to `load_image` where appropriate. They realize that nothing is

At this point, Alan wants to put the downloaded image onto the screen, which in this project means putting it into a `Node` of the current `World`. A `World` is a bundle of global state that's passed around as things are loaded, rendered, and scripts are executed. It looks like this:

```rust,ignore
```rust

/// All of the player's global state.
pub struct World<'a> {
Expand All @@ -50,7 +50,7 @@ pub struct World<'a> {

In synchronous code, this was perfectly fine. Alan figures it'll be fine in async code, too. So Alan adds the world as a function parameter and everything else needed to parse an image and add it to our list of nodes:

```rust,ignore
```rust
async fn load_image(src: String, inside_of: usize, world: &mut World<'_>) {
let request = make_request(&url);
let data = window().unwrap().fetch_with_request(&request).await.unwrap().etc.etc.etc;
Expand All @@ -73,7 +73,7 @@ error[E0597]: `world` does not live long enough

Hmm, okay, that's kind of odd. We can pass a `World` to a regular function just fine - why do we have a problem here? Alan glances over at `loader.rs`...

```rust,ignore
```rust
fn attach_image_from_net(world: &mut World<'_>, args: &[Value]) -> Result<Value, Error> {
let this = args.get(0).coerce_to_object()?;
let url = args.get(1).coerce_to_string()?;
Expand All @@ -86,7 +86,7 @@ Hmm, the error is in that last line. `spawn_local` is a thing Alan had to put in

Alan has a hunch that this `spawn_local` thing might be causing a problem, so Alan reads the documentation. The function signature seems particularly suspicious:

```rust,ignore
```rust
pub fn spawn_local<F>(future: F)
where
F: Future<Output = ()> + 'static
Expand All @@ -96,7 +96,7 @@ So, `spawn_local` only works with futures that return nothing - so far, so good

Barbara explains that when you borrow a value in a closure, the closure doesn't gain the lifetime of that borrow. Instead, the borrow comes with it's own lifetime, separate from the closure's. The only time a closure can have a non-`'static` lifetime is if one or more of its borrows is *not* provided by it's caller, like so:

```rust,ignore
```rust
fn benchmark_sort() -> usize {
let mut num_times_called = 0;
let test_values = vec![1,3,5,31,2,-13,10,16];
Expand All @@ -114,7 +114,7 @@ The closure passed to `sort_by` has to copy or borrow anything not passed into i

Async functions, it turns out, *act like closures that don't take parameters*! They *have to*, because all `Future`s have to implement the same trait method `poll`:

```rust,ignore
```rust
pub trait Future {
type Output;

Expand All @@ -126,7 +126,7 @@ When you call an async function, all of it's parameters are copied or borrowed i

Barbara suggests changing all of the async function's parameters to be owned types. Alan asks Grace, who architected this project. Grace recommends holding a reference to the `Plugin` that owns the `World`, and then borrowing it whenever you need the `World`. That ultimately looks like the following:

```rust,ignore
```rust
async fn load_image(src: String, inside_of: usize, player: Arc<Mutex<Player>>) {
let request = make_request(&url);
let data = window().unwrap().fetch_with_request(&request).await.unwrap().etc.etc.etc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ His trust in the compiler solidifies further the more he codes in Rust.
### The first async project
Alan now starts with his first async project. He sees that there is no async in the standard library, but after googling for "rust async file open", he finds 'async_std', a crate that provides some async versions of the standard library functions.
He has some code written that asynchronously interacts with some files:
```rust,ignore
```rust
use async_std::fs::File;
use async_std::prelude::*;

Expand All @@ -33,7 +33,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
```
But now the compiler complains that `await` is only allowed in `async` functions. He now notices that all the examples use `#[async_std::main]`
as an attribute on the `main` function in order to be able to turn it into an `async main`, so he does the same to get his code compiling:
```rust,ignore
```rust
use async_std::fs::File;
use async_std::prelude::*;

Expand All @@ -55,7 +55,7 @@ The project is working like a charm.
The project Alan is building is starting to grow, and he decides to add a new feature that needs to make some API calls. He starts using `reqwest` in order to help him achieve this task.
After a lot of refactoring to make the compiler accept the program again, Alan is satisfied that his refactoring is done.
His program now boils down to:
```rust,ignore
```rust
use async_std::fs::File;
use async_std::prelude::*;

Expand Down
18 changes: 9 additions & 9 deletions src/vision/status_quo/alan_writes_a_web_framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ 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<dyn Future<...>>`. 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,ignore
```rust
// For reference, the framework defines these type aliases.
pub type HandlerResult = Result<(State, Response<Body>), (State, HandlerError)>;
pub type HandlerFuture = dyn Future<Output = HandlerResult> + Send;
Expand All @@ -43,15 +43,15 @@ fn get_products_handler(state: State) -> Pin<Box<HandlerFuture>> {
}
```
and then it is registered like this:
```rust,ignore
```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. 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()`.

```rust,ignore
```rust
fn to_async<H, Fut>(self, handler: H)
where
Self: Sized,
Expand All @@ -62,13 +62,13 @@ Rather than switching YouBuy to a different web framework, Alan decides to contr
}
```
The handler registration then becomes:
```rust,ignore
```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,ignore
```rust
// Type the library again, in case you've forgotten:
pub type HandlerResult = Result<(State, Response<Body>), (State, HandlerError)>;

Expand All @@ -90,7 +90,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 to use `?` in a handler, like this:

```rust,ignore
```rust
async fn get_products_handler(state: State) -> HandlerResult {
use crate::schema::products::dsl::*;

Expand All @@ -117,7 +117,7 @@ error[E0277]: `?` couldn't convert the error to `(gotham::state::State, HandlerE

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,ignore
```rust
async fn get_products_handler(state: &mut State) -> Result<Response<Body>, HandlerError> {
use crate::schema::products::dsl::*;

Expand All @@ -131,7 +131,7 @@ async fn get_products_handler(state: &mut State) -> Result<Response<Body>, Handl
}
```
and then register it with:
```rust,ignore
```rust
route.get("/").to_async_borrowing(get_products_handler);
```

Expand All @@ -141,7 +141,7 @@ Shortly afterwards, someone raises a bug about `?`, and a few other web framewor

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)):

```rust,ignore
```rust
pub trait AsyncHandlerFn<'a> {
type Res: IntoResponse + 'static;
type Fut: std::future::Future<Output = Result<Self::Res, HandlerError>> + Send + 'a;
Expand Down
7 changes: 4 additions & 3 deletions src/vision/status_quo/barbara_anguishes_over_http.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ Alan, having watched Barbara stare off into the distance for what felt like a ha
## 🤔 Frequently Asked Questions

### **What are the morals of the story?**
* What is a very mundane and simple decision in many other languages, picking an HTTP library, requires users to contemplate *many* different considerations.
* There is no practical way to choose an HTTP library that will serve most of the ecosystem. Sync/Async, competing runtimes, etc. - someone will always be left out.
* HTTP is a small implementation detail of this library, but it is a HUGE decision that will ultimately be the biggest factor in who can adopt their library.

* What is a very mundane and simple decision in many other languages, picking an HTTP library, requires users to contemplate *many* different considerations.
* There is no practical way to choose an HTTP library that will serve most of the ecosystem. Sync/Async, competing runtimes, etc. - someone will always be left out.
* HTTP is a small implementation detail of this library, but it is a HUGE decision that will ultimately be the biggest factor in who can adopt their library.

### **What are the sources for this story?**
Based on the author's personal experience of taking newcomers to Rust through the decision making process of picking an HTTP implementation for a library.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ kernel does not expose a Future-based interface, so Barbara has to implement
`Future` by hand rather than using async/await syntax. She starts with a
skeleton:

```rust,ignore
```rust
/// Passes `buffer` to the kernel, and prints it to the console. Returns a
/// future that returns `buffer` when the print is complete. The caller must
/// call kernel_ready_for_callbacks() when it is ready for the future to return.
Expand All @@ -61,7 +61,7 @@ Note: All error handling is omitted to keep things understandable.

Barbara begins to implement `print_buffer`:

```rust,ignore
```rust
fn print_buffer(buffer: &'static mut [u8]) -> PrintFuture {
kernel_set_print_callback(callback);
kernel_start_print(buffer);
Expand All @@ -76,7 +76,7 @@ extern fn callback() {

So far so good. Barbara then works on `poll`:

```rust,ignore
```rust
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
if kernel_is_print_done() {
return Poll::Ready(kernel_get_buffer_back());
Expand All @@ -92,7 +92,7 @@ somewhere! Barbara puts the `Waker` in a global variable so the callback can
find it (this is fine because the app is single threaded and callbacks do NOT
interrupt execution the way Unix signals do):

```rust,ignore
```rust
static mut PRINT_WAKER: Option<Waker> = None;

extern fn callback() {
Expand All @@ -104,7 +104,7 @@ extern fn callback() {

She then modifies `poll` to set `PRINT_WAKER`:

```rust,ignore
```rust
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
if kernel_is_print_done() {
return Poll::Ready(kernel_get_buffer_back());
Expand Down
Loading