Skip to content

Commit

Permalink
feat: add async
Browse files Browse the repository at this point in the history
  • Loading branch information
katopz committed Sep 14, 2023
1 parent 7063f54 commit ea21ab1
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
- [Generic Array](rust/r3/generic-array.md)
- [AsRef](rust/r3/as-ref.md)
- [Callback](rust/r3/callback.md)
- [Async](rust/r3/async.md)
- [R2 - Expert](rust/r2/mod.md)
- [Hello Github Action](rust/r2/hello-github-action.md)
- [Hello Actix CloudRun](rust/r2/hello-actix-cloudrun.md)
Expand Down
118 changes: 118 additions & 0 deletions src/rust/r3/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Async

> 🤔 refer to [The State of Async Rust](https://corrode.dev/blog/async/)
## The State

```mermaid
graph LR;
A("Async madness")
A-.Try to cover Rust standard.->C("async-std<br/>abandoned 😱")
A-.Try to cover any runtime.->B("Tokio<br/>Multi threaded by default = complex 🫠")
```

## Async vs Threads

### Threads

```rust,editable
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::{thread, time};
// This fn could be used by both async and sync callers,
// eliminating the need for an asynchronous runtime.
fn read_contents<T: AsRef<Path>>(file: T) -> Result<String, Box<dyn Error>> {
let mut file = File::open(file)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
return Ok(contents);
}
fn main() {
thread::scope(|scope| {
// worker thread 1
scope.spawn(|| {
let contents = read_contents("foo.txt");
// do something with contents
});
// worker thread 2
scope.spawn(|| {
let contents = read_contents("bar.txt");
// ...
});
// worker thread 3
scope.spawn(|| {
let contents = read_contents("baz.txt");
// ...
});
});
// Threads get joined automatically
}
```

### Async

```rust
#[tokio::main]
async fn main() {
// This will print a warning, but compile and do nothing at runtime
read_contents("foo.txt");
// futures do nothing unless you `.await` or poll them
}
```

> 🤔 refer to [Asynchronous Programming in Rust](https://rust-lang.github.io/async-book/01_getting_started/02_why_async.html#async-vs-threads-in-rust)
![](/assets/kat.png) If you don't need async for performance reasons, threads can often be the simpler alternative.

## Best practices

1. `synchronous` by default.
1. opt into `async` as needed.
1. less main with `#[tokio::main]`.

```rust,no_run
#[get("/")]
fn index() -> impl Response {
let users = db::get_users();
Response::ok().body(render_template("index.html", users))
}
// A route, which profits from concurrent IO
// It sends multiple requests to an external API and aggregates the results
// Note how the function itself is does not need to be async
#[get("/users")]
fn users(count: usize) -> impl Response {
// Start a local, single-threaded runtime with smol's async-executor
let rt = smol::LocalExecutor::new();
// Run the async code on the runtime
let results = rt.run(async {
let mut results = Vec::new();
for id in 0..count {
let result = reqwest::get(format!("https://api.example.com/users/{}", id)).await?;
results.push(result);
}
Ok(results)
});
Response::ok().body(render_template("users.html", results))
}
// This does not need to be async either!
// In the background, it might use a thread pool to handle multiple requests
fn main() -> Result<()> {
let app = App::new()
.mount("/", index)
.mount("/users", users)
.run();
}
```

![](/assets/kat.png) Read more 👉 [Asynchronous Programming in Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html)

0 comments on commit ea21ab1

Please sign in to comment.