Skip to content

Commit 6c5b671

Browse files
authored
Merge pull request #152 from rylev/small-changes
Small cosmetic changes
2 parents cbfe0a3 + f36e9ed commit 6c5b671

10 files changed

+56
-57
lines changed

src/vision/shiny_future/barbara_makes_a_wish.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Barbara now remembers hearing something about a `wish4-async-insight` crate, whi
2222

2323
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.
2424

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

3232
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.
3333

34-
```rust,ignore
34+
```rust
3535
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
3636
insight::init(listen_port => 8080); // new code, leveraging keyword arguments feature added in 2024
3737
...

src/vision/status_quo/alan_finds_database_drops_hard.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee
1111

1212
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:
1313

14-
```rust,ignore
14+
```rust
1515
use sqlx::Connection;
1616

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

4242
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:
4343

44-
```rust,ignore
44+
```rust
4545
use sqlx::Connection;
4646

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

7777
## 🤔 Frequently Asked Questions
7878

79-
*Here are some standard FAQ to get you started. Feel free to add more!*
80-
8179
### **What are the morals of the story?**
8280
* 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.
8381
* The story demonstrates solid research steps that Alan uses to understand and resolve his problem.

src/vision/status_quo/alan_hates_writing_a_stream.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ After a couple weeks learning Rust basics, Alan quickly understands `async` and
1414

1515
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`:
1616

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

2727
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:
2828

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

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

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

76-
```rust,ignore
76+
```rust
7777
struct SigningFile;
7878

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

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

104-
```rust,ignore
104+
```rust
105105
struct SigningFile {
106106
state: State,
107107
file: ReaderStream,
@@ -118,7 +118,7 @@ enum State {
118118

119119
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.
120120

121-
```rust,ignore
121+
```rust
122122
struct SigningFile {
123123
state: State,
124124
}
@@ -132,7 +132,7 @@ enum State {
132132

133133
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:
134134

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

167167
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:
168168

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

189189
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`:
190190

191-
```rust,ignore
191+
```rust
192192
loop {
193193
match self.state {
194194
State::File(ref mut file, ref mut sig) => {

src/vision/status_quo/alan_lost_the_world.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Alan heard about a project to reimplement a deprecated browser plugin using Rust
1515
3. `await` the `Future` in an `async` function
1616
4. Do whatever they want with the resulting data
1717

18-
```rust,ignore
18+
```rust
1919
use web_sys::{Request, window};
2020

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

3535
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:
3636

37-
```rust,ignore
37+
```rust
3838

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

5151
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:
5252

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

7474
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`...
7575

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

8787
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:
8888

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

9797
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:
9898

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

115115
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`:
116116

117-
```rust,ignore
117+
```rust
118118
pub trait Future {
119119
type Output;
120120

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

127127
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:
128128

129-
```rust,ignore
129+
```rust
130130
async fn load_image(src: String, inside_of: usize, player: Arc<Mutex<Player>>) {
131131
let request = make_request(&url);
132132
let data = window().unwrap().fetch_with_request(&request).await.unwrap().etc.etc.etc;

src/vision/status_quo/alan_started_trusting_the_rust_compiler_but_then_async.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ His trust in the compiler solidifies further the more he codes in Rust.
2121
### The first async project
2222
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.
2323
He has some code written that asynchronously interacts with some files:
24-
```rust,ignore
24+
```rust
2525
use async_std::fs::File;
2626
use async_std::prelude::*;
2727

@@ -33,7 +33,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
3333
```
3434
But now the compiler complains that `await` is only allowed in `async` functions. He now notices that all the examples use `#[async_std::main]`
3535
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:
36-
```rust,ignore
36+
```rust
3737
use async_std::fs::File;
3838
use async_std::prelude::*;
3939

@@ -55,7 +55,7 @@ The project is working like a charm.
5555
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.
5656
After a lot of refactoring to make the compiler accept the program again, Alan is satisfied that his refactoring is done.
5757
His program now boils down to:
58-
```rust,ignore
58+
```rust
5959
use async_std::fs::File;
6060
use async_std::prelude::*;
6161

src/vision/status_quo/alan_writes_a_web_framework.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ If you would like to expand on this story, or adjust the answers to the FAQ, fee
1919

2020
[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)):
2121

22-
```rust,ignore
22+
```rust
2323
// For reference, the framework defines these type aliases.
2424
pub type HandlerResult = Result<(State, Response<Body>), (State, HandlerError)>;
2525
pub type HandlerFuture = dyn Future<Output = HandlerResult> + Send;
@@ -43,15 +43,15 @@ fn get_products_handler(state: State) -> Pin<Box<HandlerFuture>> {
4343
}
4444
```
4545
and then it is registered like this:
46-
```rust,ignore
46+
```rust
4747
router_builder.get("/").to(get_products_handler);
4848
```
4949

5050
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).
5151

5252
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()`.
5353

54-
```rust,ignore
54+
```rust
5555
fn to_async<H, Fut>(self, handler: H)
5656
where
5757
Self: Sized,
@@ -62,13 +62,13 @@ Rather than switching YouBuy to a different web framework, Alan decides to contr
6262
}
6363
```
6464
The handler registration then becomes:
65-
```rust,ignore
65+
```rust
6666
router_builder.get("/").to_async(get_products_handler);
6767
```
6868

6969
This allows him to strip out the async blocks in his handlers and use `async fn` instead.
7070

71-
```rust,ignore
71+
```rust
7272
// Type the library again, in case you've forgotten:
7373
pub type HandlerResult = Result<(State, Response<Body>), (State, HandlerError)>;
7474

@@ -90,7 +90,7 @@ async fn get_products_handler(state: State) -> HandlerResult {
9090

9191
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:
9292

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

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

118118
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:
119119

120-
```rust,ignore
120+
```rust
121121
async fn get_products_handler(state: &mut State) -> Result<Response<Body>, HandlerError> {
122122
use crate::schema::products::dsl::*;
123123

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

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

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

144-
```rust,ignore
144+
```rust
145145
pub trait AsyncHandlerFn<'a> {
146146
type Res: IntoResponse + 'static;
147147
type Fut: std::future::Future<Output = Result<Self::Res, HandlerError>> + Send + 'a;

src/vision/status_quo/barbara_anguishes_over_http.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ Alan, having watched Barbara stare off into the distance for what felt like a ha
3131
## 🤔 Frequently Asked Questions
3232

3333
### **What are the morals of the story?**
34-
* What is a very mundane and simple decision in many other languages, picking an HTTP library, requires users to contemplate *many* different considerations.
35-
* 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.
36-
* 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.
34+
35+
* What is a very mundane and simple decision in many other languages, picking an HTTP library, requires users to contemplate *many* different considerations.
36+
* 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.
37+
* 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.
3738

3839
### **What are the sources for this story?**
3940
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.

src/vision/status_quo/barbara_carefully_dismisses_embedded_future.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ kernel does not expose a Future-based interface, so Barbara has to implement
3636
`Future` by hand rather than using async/await syntax. She starts with a
3737
skeleton:
3838

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

6262
Barbara begins to implement `print_buffer`:
6363

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

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

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

95-
```rust,ignore
95+
```rust
9696
static mut PRINT_WAKER: Option<Waker> = None;
9797

9898
extern fn callback() {
@@ -104,7 +104,7 @@ extern fn callback() {
104104

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

107-
```rust,ignore
107+
```rust
108108
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
109109
if kernel_is_print_done() {
110110
return Poll::Ready(kernel_get_buffer_back());

0 commit comments

Comments
 (0)