You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-06-26 02:31:00 +02:00
Format all Markdown files with dprint
(#1157)
This is the result of running `dprint fmt` after removing `src/` from the list of excluded directories. This also reformats the Rust code: we might want to tweak this a bit in the future since some of the changes removes the hand-formatting. Of course, this formatting can be seen as a mis-feature, so maybe this is good overall. Thanks to mdbook-i18n-helpers 0.2, the POT file is nearly unchanged after this, meaning that all existing translations remain valid! A few messages were changed because of stray whitespace characters: msgid "" "Slices always borrow from another object. In this example, `a` has to remain " -"'alive' (in scope) for at least as long as our slice. " +"'alive' (in scope) for at least as long as our slice." msgstr "" The formatting is enforced in CI and we will have to see how annoying this is in practice for the many contributors. If it becomes annoying, we should look into fixing dprint/check#11 so that `dprint` can annotate the lines that need fixing directly, then I think we can consider more strict formatting checks. I added more customization to `rustfmt.toml`. This is to better emulate the dense style used in the course: - `max_width = 85` allows lines to take up the full width available in our code blocks (when taking margins and the line numbers into account). - `wrap_comments = true` ensures that we don't show very long comments in the code examples. I edited some comments to shorten them and avoid unnecessary line breaks — please trim other unnecessarily long comments when you see them! Remember we're writing code for slides 😄 - `use_small_heuristics = "Max"` allows for things like struct literals and if-statements to take up the full line width configured above. The formatting settings apply to all our Rust code right now — I think we could improve this with https://github.com/dprint/dprint/issues/711 which lets us add per-directory `dprint` configuration files. However, the `inherit: true` setting is not yet implemented (as far as I can tell), so a nested configuration file will have to copy most or all of the top-level file.
This commit is contained in:
@ -24,25 +24,25 @@ fn main() {
|
||||
|
||||
Key points:
|
||||
|
||||
* Note that this is a simplified example to show the syntax. There is no long
|
||||
- Note that this is a simplified example to show the syntax. There is no long
|
||||
running operation or any real concurrency in it!
|
||||
|
||||
* What is the return type of an async call?
|
||||
* Use `let future: () = async_main(10);` in `main` to see the type.
|
||||
- What is the return type of an async call?
|
||||
- Use `let future: () = async_main(10);` in `main` to see the type.
|
||||
|
||||
* The "async" keyword is syntactic sugar. The compiler replaces the return type
|
||||
with a future.
|
||||
- The "async" keyword is syntactic sugar. The compiler replaces the return type
|
||||
with a future.
|
||||
|
||||
* You cannot make `main` async, without additional instructions to the compiler
|
||||
- You cannot make `main` async, without additional instructions to the compiler
|
||||
on how to use the returned future.
|
||||
|
||||
* You need an executor to run async code. `block_on` blocks the current thread
|
||||
until the provided future has run to completion.
|
||||
- You need an executor to run async code. `block_on` blocks the current thread
|
||||
until the provided future has run to completion.
|
||||
|
||||
* `.await` asynchronously waits for the completion of another operation. Unlike
|
||||
- `.await` asynchronously waits for the completion of another operation. Unlike
|
||||
`block_on`, `.await` doesn't block the current thread.
|
||||
|
||||
* `.await` can only be used inside an `async` function (or block; these are
|
||||
introduced later).
|
||||
- `.await` can only be used inside an `async` function (or block; these are
|
||||
introduced later).
|
||||
|
||||
</details>
|
||||
|
@ -32,18 +32,18 @@ async fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
* Change the channel size to `3` and see how it affects the execution.
|
||||
- Change the channel size to `3` and see how it affects the execution.
|
||||
|
||||
* Overall, the interface is similar to the `sync` channels as seen in the
|
||||
- Overall, the interface is similar to the `sync` channels as seen in the
|
||||
[morning class](concurrency/channels.md).
|
||||
|
||||
* Try removing the `std::mem::drop` call. What happens? Why?
|
||||
- Try removing the `std::mem::drop` call. What happens? Why?
|
||||
|
||||
* The [Flume](https://docs.rs/flume/latest/flume/) crate has channels that
|
||||
- The [Flume](https://docs.rs/flume/latest/flume/) crate has channels that
|
||||
implement both `sync` and `async` `send` and `recv`. This can be convenient
|
||||
for complex applications with both IO and heavy CPU processing tasks.
|
||||
|
||||
* What makes working with `async` channels preferable is the ability to combine
|
||||
- What makes working with `async` channels preferable is the ability to combine
|
||||
them with other `future`s to combine them and create complex control flow.
|
||||
|
||||
</details>
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Join
|
||||
|
||||
A join operation waits until all of a set of futures are ready, and
|
||||
returns a collection of their results. This is similar to `Promise.all` in
|
||||
JavaScript or `asyncio.gather` in Python.
|
||||
A join operation waits until all of a set of futures are ready, and returns a
|
||||
collection of their results. This is similar to `Promise.all` in JavaScript or
|
||||
`asyncio.gather` in Python.
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use anyhow::Result;
|
||||
@ -35,16 +35,17 @@ async fn main() {
|
||||
|
||||
Copy this example into your prepared `src/main.rs` and run it from there.
|
||||
|
||||
* For multiple futures of disjoint types, you can use `std::future::join!` but
|
||||
- For multiple futures of disjoint types, you can use `std::future::join!` but
|
||||
you must know how many futures you will have at compile time. This is
|
||||
currently in the `futures` crate, soon to be stabilised in `std::future`.
|
||||
|
||||
* The risk of `join` is that one of the futures may never resolve, this would
|
||||
cause your program to stall.
|
||||
- The risk of `join` is that one of the futures may never resolve, this would
|
||||
cause your program to stall.
|
||||
|
||||
* You can also combine `join_all` with `join!` for instance to join all requests
|
||||
- You can also combine `join_all` with `join!` for instance to join all requests
|
||||
to an http service as well as a database query. Try adding a
|
||||
`tokio::time::sleep` to the future, using `futures::join!`. This is not a
|
||||
timeout (that requires `select!`, explained in the next chapter), but demonstrates `join!`.
|
||||
timeout (that requires `select!`, explained in the next chapter), but
|
||||
demonstrates `join!`.
|
||||
|
||||
</details>
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
A select operation waits until any of a set of futures is ready, and responds to
|
||||
that future's result. In JavaScript, this is similar to `Promise.race`. In
|
||||
Python, it compares to `asyncio.wait(task_set,
|
||||
return_when=asyncio.FIRST_COMPLETED)`.
|
||||
Python, it compares to
|
||||
`asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED)`.
|
||||
|
||||
Similar to a match statement, the body of `select!` has a number of arms, each
|
||||
of the form `pattern = future => statement`. When the `future` is ready, the
|
||||
@ -36,17 +36,11 @@ async fn main() {
|
||||
let (dog_sender, dog_receiver) = mpsc::channel(32);
|
||||
tokio::spawn(async move {
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
cat_sender
|
||||
.send(String::from("Felix"))
|
||||
.await
|
||||
.expect("Failed to send cat.");
|
||||
cat_sender.send(String::from("Felix")).await.expect("Failed to send cat.");
|
||||
});
|
||||
tokio::spawn(async move {
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
dog_sender
|
||||
.send(String::from("Rex"))
|
||||
.await
|
||||
.expect("Failed to send dog.");
|
||||
dog_sender.send(String::from("Rex")).await.expect("Failed to send dog.");
|
||||
});
|
||||
|
||||
let winner = first_animal_to_finish_race(cat_receiver, dog_receiver)
|
||||
@ -59,21 +53,21 @@ async fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
* In this example, we have a race between a cat and a dog.
|
||||
- In this example, we have a race between a cat and a dog.
|
||||
`first_animal_to_finish_race` listens to both channels and will pick whichever
|
||||
arrives first. Since the dog takes 50ms, it wins against the cat that
|
||||
take 500ms.
|
||||
arrives first. Since the dog takes 50ms, it wins against the cat that take
|
||||
500ms.
|
||||
|
||||
* You can use `oneshot` channels in this example as the channels are supposed to
|
||||
- You can use `oneshot` channels in this example as the channels are supposed to
|
||||
receive only one `send`.
|
||||
|
||||
* Try adding a deadline to the race, demonstrating selecting different sorts of
|
||||
- Try adding a deadline to the race, demonstrating selecting different sorts of
|
||||
futures.
|
||||
|
||||
* Note that `select!` drops unmatched branches, which cancels their futures.
|
||||
It is easiest to use when every execution of `select!` creates new futures.
|
||||
- Note that `select!` drops unmatched branches, which cancels their futures. It
|
||||
is easiest to use when every execution of `select!` creates new futures.
|
||||
|
||||
* An alternative is to pass `&mut future` instead of the future itself, but
|
||||
this can lead to issues, further discussed in the pinning slide.
|
||||
- An alternative is to pass `&mut future` instead of the future itself, but
|
||||
this can lead to issues, further discussed in the pinning slide.
|
||||
|
||||
</details>
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Futures
|
||||
|
||||
[`Future`](https://doc.rust-lang.org/std/future/trait.Future.html)
|
||||
is a trait, implemented by objects that represent an operation that may not be
|
||||
complete yet. A future can be polled, and `poll` returns a
|
||||
[`Future`](https://doc.rust-lang.org/std/future/trait.Future.html) is a trait,
|
||||
implemented by objects that represent an operation that may not be complete yet.
|
||||
A future can be polled, and `poll` returns a
|
||||
[`Poll`](https://doc.rust-lang.org/std/task/enum.Poll.html).
|
||||
|
||||
```rust
|
||||
@ -29,16 +29,16 @@ pause until that Future is ready, and then evaluates to its output.
|
||||
|
||||
<details>
|
||||
|
||||
* The `Future` and `Poll` types are implemented exactly as shown; click the
|
||||
- The `Future` and `Poll` types are implemented exactly as shown; click the
|
||||
links to show the implementations in the docs.
|
||||
|
||||
* We will not get to `Pin` and `Context`, as we will focus on writing async
|
||||
- We will not get to `Pin` and `Context`, as we will focus on writing async
|
||||
code, rather than building new async primitives. Briefly:
|
||||
|
||||
* `Context` allows a Future to schedule itself to be polled again when an
|
||||
- `Context` allows a Future to schedule itself to be polled again when an
|
||||
event occurs.
|
||||
|
||||
* `Pin` ensures that the Future isn't moved in memory, so that pointers into
|
||||
- `Pin` ensures that the Future isn't moved in memory, so that pointers into
|
||||
that future remain valid. This is required to allow references to remain
|
||||
valid after an `.await`.
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
# Pitfalls of async/await
|
||||
|
||||
Async / await provides convenient and efficient abstraction for concurrent asynchronous programming. However, the async/await model in Rust also comes with its share of pitfalls and footguns. We illustrate some of them in this chapter:
|
||||
Async / await provides convenient and efficient abstraction for concurrent
|
||||
asynchronous programming. However, the async/await model in Rust also comes with
|
||||
its share of pitfalls and footguns. We illustrate some of them in this chapter:
|
||||
|
||||
- [Blocking the Executor](pitfalls/blocking-executor.md)
|
||||
- [Pin](pitfalls/pin.md)
|
||||
|
@ -1,8 +1,10 @@
|
||||
# Async Traits
|
||||
|
||||
Async methods in traits are not yet supported in the stable channel ([An experimental feature exists in nightly and should be stabilized in the mid term.](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html))
|
||||
Async methods in traits are not yet supported in the stable channel
|
||||
([An experimental feature exists in nightly and should be stabilized in the mid term.](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html))
|
||||
|
||||
The crate [async_trait](https://docs.rs/async-trait/latest/async_trait/) provides a workaround through a macro:
|
||||
The crate [async_trait](https://docs.rs/async-trait/latest/async_trait/)
|
||||
provides a workaround through a macro:
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use async_trait::async_trait;
|
||||
@ -25,7 +27,10 @@ impl Sleeper for FixedSleeper {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_all_sleepers_multiple_times(sleepers: Vec<Box<dyn Sleeper>>, n_times: usize) {
|
||||
async fn run_all_sleepers_multiple_times(
|
||||
sleepers: Vec<Box<dyn Sleeper>>,
|
||||
n_times: usize,
|
||||
) {
|
||||
for _ in 0..n_times {
|
||||
println!("running all sleepers..");
|
||||
for sleeper in &sleepers {
|
||||
@ -46,18 +51,18 @@ async fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<details>
|
||||
|
||||
* `async_trait` is easy to use, but note that it's using heap allocations to
|
||||
- `async_trait` is easy to use, but note that it's using heap allocations to
|
||||
achieve this. This heap allocation has performance overhead.
|
||||
|
||||
* The challenges in language support for `async trait` are deep Rust and
|
||||
- The challenges in language support for `async trait` are deep Rust and
|
||||
probably not worth describing in-depth. Niko Matsakis did a good job of
|
||||
explaining them in [this
|
||||
post](https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/)
|
||||
explaining them in
|
||||
[this post](https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/)
|
||||
if you are interested in digging deeper.
|
||||
|
||||
* Try creating a new sleeper struct that will sleep for a random amount of time
|
||||
- Try creating a new sleeper struct that will sleep for a random amount of time
|
||||
and adding it to the Vec.
|
||||
|
||||
</details>
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Blocking the executor
|
||||
|
||||
Most async runtimes only allow IO tasks to run concurrently.
|
||||
This means that CPU blocking tasks will block the executor and prevent other tasks from being executed.
|
||||
An easy workaround is to use async equivalent methods where possible.
|
||||
Most async runtimes only allow IO tasks to run concurrently. This means that CPU
|
||||
blocking tasks will block the executor and prevent other tasks from being
|
||||
executed. An easy workaround is to use async equivalent methods where possible.
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use futures::future::join_all;
|
||||
@ -26,25 +26,25 @@ async fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
* Run the code and see that the sleeps happen consecutively rather than
|
||||
- Run the code and see that the sleeps happen consecutively rather than
|
||||
concurrently.
|
||||
|
||||
* The `"current_thread"` flavor puts all tasks on a single thread. This makes the
|
||||
effect more obvious, but the bug is still present in the multi-threaded
|
||||
- The `"current_thread"` flavor puts all tasks on a single thread. This makes
|
||||
the effect more obvious, but the bug is still present in the multi-threaded
|
||||
flavor.
|
||||
|
||||
* Switch the `std::thread::sleep` to `tokio::time::sleep` and await its result.
|
||||
- Switch the `std::thread::sleep` to `tokio::time::sleep` and await its result.
|
||||
|
||||
* Another fix would be to `tokio::task::spawn_blocking` which spawns an actual
|
||||
- Another fix would be to `tokio::task::spawn_blocking` which spawns an actual
|
||||
thread and transforms its handle into a future without blocking the executor.
|
||||
|
||||
* You should not think of tasks as OS threads. They do not map 1 to 1 and most
|
||||
- You should not think of tasks as OS threads. They do not map 1 to 1 and most
|
||||
executors will allow many tasks to run on a single OS thread. This is
|
||||
particularly problematic when interacting with other libraries via FFI, where
|
||||
that library might depend on thread-local storage or map to specific OS
|
||||
threads (e.g., CUDA). Prefer `tokio::task::spawn_blocking` in such situations.
|
||||
|
||||
* Use sync mutexes with care. Holding a mutex over an `.await` may cause another
|
||||
- Use sync mutexes with care. Holding a mutex over an `.await` may cause another
|
||||
task to block, and that task may be running on the same thread.
|
||||
|
||||
</details>
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Cancellation
|
||||
|
||||
Dropping a future implies it can never be polled again. This is called *cancellation*
|
||||
and it can occur at any `await` point. Care is needed to ensure the system works
|
||||
correctly even when futures are cancelled. For example, it shouldn't deadlock or
|
||||
lose data.
|
||||
Dropping a future implies it can never be polled again. This is called
|
||||
_cancellation_ and it can occur at any `await` point. Care is needed to ensure
|
||||
the system works correctly even when futures are cancelled. For example, it
|
||||
shouldn't deadlock or lose data.
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use std::io::{self, ErrorKind};
|
||||
@ -29,7 +29,7 @@ impl LinesReader {
|
||||
}
|
||||
}
|
||||
if bytes.is_empty() {
|
||||
return Ok(None)
|
||||
return Ok(None);
|
||||
}
|
||||
let s = String::from_utf8(bytes)
|
||||
.map_err(|_| io::Error::new(ErrorKind::InvalidData, "not UTF-8"))?;
|
||||
@ -69,46 +69,49 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
<details>
|
||||
|
||||
* The compiler doesn't help with cancellation-safety. You need to read API
|
||||
- The compiler doesn't help with cancellation-safety. You need to read API
|
||||
documentation and consider what state your `async fn` holds.
|
||||
|
||||
* Unlike `panic` and `?`, cancellation is part of normal control flow
|
||||
(vs error-handling).
|
||||
- Unlike `panic` and `?`, cancellation is part of normal control flow (vs
|
||||
error-handling).
|
||||
|
||||
* The example loses parts of the string.
|
||||
- The example loses parts of the string.
|
||||
|
||||
* Whenever the `tick()` branch finishes first, `next()` and its `buf` are dropped.
|
||||
- Whenever the `tick()` branch finishes first, `next()` and its `buf` are
|
||||
dropped.
|
||||
|
||||
* `LinesReader` can be made cancellation-safe by making `buf` part of the struct:
|
||||
```rust,compile_fail
|
||||
struct LinesReader {
|
||||
stream: DuplexStream,
|
||||
bytes: Vec<u8>,
|
||||
buf: [u8; 1],
|
||||
- `LinesReader` can be made cancellation-safe by making `buf` part of the
|
||||
struct:
|
||||
```rust,compile_fail
|
||||
struct LinesReader {
|
||||
stream: DuplexStream,
|
||||
bytes: Vec<u8>,
|
||||
buf: [u8; 1],
|
||||
}
|
||||
|
||||
impl LinesReader {
|
||||
fn new(stream: DuplexStream) -> Self {
|
||||
Self { stream, bytes: Vec::new(), buf: [0] }
|
||||
}
|
||||
|
||||
impl LinesReader {
|
||||
fn new(stream: DuplexStream) -> Self {
|
||||
Self { stream, bytes: Vec::new(), buf: [0] }
|
||||
}
|
||||
async fn next(&mut self) -> io::Result<Option<String>> {
|
||||
// prefix buf and bytes with self.
|
||||
// ...
|
||||
let raw = std::mem::take(&mut self.bytes);
|
||||
let s = String::from_utf8(raw)
|
||||
// ...
|
||||
}
|
||||
async fn next(&mut self) -> io::Result<Option<String>> {
|
||||
// prefix buf and bytes with self.
|
||||
// ...
|
||||
let raw = std::mem::take(&mut self.bytes);
|
||||
let s = String::from_utf8(raw)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
* [`Interval::tick`](https://docs.rs/tokio/latest/tokio/time/struct.Interval.html#method.tick)
|
||||
is cancellation-safe because it keeps track of whether a tick has been 'delivered'.
|
||||
- [`Interval::tick`](https://docs.rs/tokio/latest/tokio/time/struct.Interval.html#method.tick)
|
||||
is cancellation-safe because it keeps track of whether a tick has been
|
||||
'delivered'.
|
||||
|
||||
* [`AsyncReadExt::read`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncReadExt.html#method.read)
|
||||
- [`AsyncReadExt::read`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncReadExt.html#method.read)
|
||||
is cancellation-safe because it either returns or doesn't read data.
|
||||
|
||||
* [`AsyncBufReadExt::read_line`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncBufReadExt.html#method.read_line)
|
||||
is similar to the example and *isn't* cancellation-safe. See its documentation
|
||||
- [`AsyncBufReadExt::read_line`](https://docs.rs/tokio/latest/tokio/io/trait.AsyncBufReadExt.html#method.read_line)
|
||||
is similar to the example and _isn't_ cancellation-safe. See its documentation
|
||||
for details and alternatives.
|
||||
|
||||
</details>
|
||||
|
@ -1,9 +1,9 @@
|
||||
# `Pin`
|
||||
|
||||
When you await a future, all local variables (that would ordinarily be stored on
|
||||
a stack frame) are instead stored in the Future for the current async block. If your
|
||||
future has pointers to data on the stack, those pointers might get invalidated.
|
||||
This is unsafe.
|
||||
a stack frame) are instead stored in the Future for the current async block. If
|
||||
your future has pointers to data on the stack, those pointers might get
|
||||
invalidated. This is unsafe.
|
||||
|
||||
Therefore, you must guarantee that the addresses your future points to don't
|
||||
change. That is why we need to "pin" futures. Using the same future repeatedly
|
||||
@ -43,10 +43,7 @@ async fn worker(mut work_queue: mpsc::Receiver<Work>) {
|
||||
async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
work_queue
|
||||
.send(Work {
|
||||
input,
|
||||
respond_on: tx,
|
||||
})
|
||||
.send(Work { input, respond_on: tx })
|
||||
.await
|
||||
.expect("failed to send on work queue");
|
||||
rx.await.expect("failed waiting for response")
|
||||
@ -65,48 +62,49 @@ async fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
* You may recognize this as an example of the actor pattern. Actors
|
||||
typically call `select!` in a loop.
|
||||
- You may recognize this as an example of the actor pattern. Actors typically
|
||||
call `select!` in a loop.
|
||||
|
||||
* This serves as a summation of a few of the previous lessons, so take your time
|
||||
- This serves as a summation of a few of the previous lessons, so take your time
|
||||
with it.
|
||||
|
||||
* Naively add a `_ = sleep(Duration::from_millis(100)) => { println!(..) }`
|
||||
to the `select!`. This will never execute. Why?
|
||||
- Naively add a `_ = sleep(Duration::from_millis(100)) => { println!(..) }` to
|
||||
the `select!`. This will never execute. Why?
|
||||
|
||||
* Instead, add a `timeout_fut` containing that future outside of the `loop`:
|
||||
- Instead, add a `timeout_fut` containing that future outside of the `loop`:
|
||||
|
||||
```rust,compile_fail
|
||||
let mut timeout_fut = sleep(Duration::from_millis(100));
|
||||
loop {
|
||||
select! {
|
||||
..,
|
||||
_ = timeout_fut => { println!(..); },
|
||||
}
|
||||
```rust,compile_fail
|
||||
let mut timeout_fut = sleep(Duration::from_millis(100));
|
||||
loop {
|
||||
select! {
|
||||
..,
|
||||
_ = timeout_fut => { println!(..); },
|
||||
}
|
||||
```
|
||||
* This still doesn't work. Follow the compiler errors, adding `&mut` to the
|
||||
`timeout_fut` in the `select!` to work around the move, then using
|
||||
`Box::pin`:
|
||||
}
|
||||
```
|
||||
- This still doesn't work. Follow the compiler errors, adding `&mut` to the
|
||||
`timeout_fut` in the `select!` to work around the move, then using
|
||||
`Box::pin`:
|
||||
|
||||
```rust,compile_fail
|
||||
let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100)));
|
||||
loop {
|
||||
select! {
|
||||
..,
|
||||
_ = &mut timeout_fut => { println!(..); },
|
||||
}
|
||||
```rust,compile_fail
|
||||
let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100)));
|
||||
loop {
|
||||
select! {
|
||||
..,
|
||||
_ = &mut timeout_fut => { println!(..); },
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
* This compiles, but once the timeout expires it is `Poll::Ready` on every
|
||||
iteration (a fused future would help with this). Update to reset
|
||||
`timeout_fut` every time it expires.
|
||||
- This compiles, but once the timeout expires it is `Poll::Ready` on every
|
||||
iteration (a fused future would help with this). Update to reset
|
||||
`timeout_fut` every time it expires.
|
||||
|
||||
* Box allocates on the heap. In some cases, `std::pin::pin!` (only recently
|
||||
- Box allocates on the heap. In some cases, `std::pin::pin!` (only recently
|
||||
stabilized, with older code often using `tokio::pin!`) is also an option, but
|
||||
that is difficult to use for a future that is reassigned.
|
||||
|
||||
* Another alternative is to not use `pin` at all but spawn another task that will send to a `oneshot` channel every 100ms.
|
||||
- Another alternative is to not use `pin` at all but spawn another task that
|
||||
will send to a `oneshot` channel every 100ms.
|
||||
|
||||
</details>
|
||||
|
@ -1,15 +1,15 @@
|
||||
# Runtimes
|
||||
|
||||
A *runtime* provides support for performing operations asynchronously (a
|
||||
*reactor*) and is responsible for executing futures (an *executor*). Rust does not have a
|
||||
"built-in" runtime, but several options are available:
|
||||
A _runtime_ provides support for performing operations asynchronously (a
|
||||
_reactor_) and is responsible for executing futures (an _executor_). Rust does
|
||||
not have a "built-in" runtime, but several options are available:
|
||||
|
||||
* [Tokio](https://tokio.rs/): performant, with a well-developed ecosystem of
|
||||
functionality like [Hyper](https://hyper.rs/) for HTTP or
|
||||
[Tonic](https://github.com/hyperium/tonic) for gRPC.
|
||||
* [async-std](https://async.rs/): aims to be a "std for async", and includes a
|
||||
basic runtime in `async::task`.
|
||||
* [smol](https://docs.rs/smol/latest/smol/): simple and lightweight
|
||||
- [Tokio](https://tokio.rs/): performant, with a well-developed ecosystem of
|
||||
functionality like [Hyper](https://hyper.rs/) for HTTP or
|
||||
[Tonic](https://github.com/hyperium/tonic) for gRPC.
|
||||
- [async-std](https://async.rs/): aims to be a "std for async", and includes a
|
||||
basic runtime in `async::task`.
|
||||
- [smol](https://docs.rs/smol/latest/smol/): simple and lightweight
|
||||
|
||||
Several larger applications have their own runtimes. For example,
|
||||
[Fuchsia](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/src/lib/fuchsia-async/src/lib.rs)
|
||||
@ -17,11 +17,11 @@ already has one.
|
||||
|
||||
<details>
|
||||
|
||||
* Note that of the listed runtimes, only Tokio is supported in the Rust
|
||||
- Note that of the listed runtimes, only Tokio is supported in the Rust
|
||||
playground. The playground also does not permit any I/O, so most interesting
|
||||
async things can't run in the playground.
|
||||
|
||||
* Futures are "inert" in that they do not do anything (not even start an I/O
|
||||
- Futures are "inert" in that they do not do anything (not even start an I/O
|
||||
operation) unless there is an executor polling them. This differs from JS
|
||||
Promises, for example, which will run to completion even if they are never
|
||||
used.
|
||||
|
@ -1,11 +1,10 @@
|
||||
# Tokio
|
||||
|
||||
Tokio provides:
|
||||
|
||||
Tokio provides:
|
||||
|
||||
* A multi-threaded runtime for executing asynchronous code.
|
||||
* An asynchronous version of the standard library.
|
||||
* A large ecosystem of libraries.
|
||||
- A multi-threaded runtime for executing asynchronous code.
|
||||
- An asynchronous version of the standard library.
|
||||
- A large ecosystem of libraries.
|
||||
|
||||
```rust,editable,compile_fail
|
||||
use tokio::time;
|
||||
@ -30,20 +29,20 @@ async fn main() {
|
||||
|
||||
<details>
|
||||
|
||||
* With the `tokio::main` macro we can now make `main` async.
|
||||
- With the `tokio::main` macro we can now make `main` async.
|
||||
|
||||
* The `spawn` function creates a new, concurrent "task".
|
||||
- The `spawn` function creates a new, concurrent "task".
|
||||
|
||||
* Note: `spawn` takes a `Future`, you don't call `.await` on `count_to`.
|
||||
- Note: `spawn` takes a `Future`, you don't call `.await` on `count_to`.
|
||||
|
||||
**Further exploration:**
|
||||
|
||||
* Why does `count_to` not (usually) get to 10? This is an example of async
|
||||
- Why does `count_to` not (usually) get to 10? This is an example of async
|
||||
cancellation. `tokio::spawn` returns a handle which can be awaited to wait
|
||||
until it finishes.
|
||||
|
||||
* Try `count_to(10).await` instead of spawning.
|
||||
- Try `count_to(10).await` instead of spawning.
|
||||
|
||||
* Try awaiting the task returned from `tokio::spawn`.
|
||||
- Try awaiting the task returned from `tokio::spawn`.
|
||||
|
||||
</details>
|
||||
|
@ -14,7 +14,7 @@ use tokio::net::TcpListener;
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
let listener = TcpListener::bind("127.0.0.1:6142").await?;
|
||||
println!("listening on port 6142");
|
||||
println!("listening on port 6142");
|
||||
|
||||
loop {
|
||||
let (mut socket, addr) = listener.accept().await?;
|
||||
@ -51,15 +51,18 @@ async fn main() -> io::Result<()> {
|
||||
|
||||
Copy this example into your prepared `src/main.rs` and run it from there.
|
||||
|
||||
Try connecting to it with a TCP connection tool like [nc](https://www.unix.com/man-page/linux/1/nc/) or [telnet](https://www.unix.com/man-page/linux/1/telnet/).
|
||||
Try connecting to it with a TCP connection tool like
|
||||
[nc](https://www.unix.com/man-page/linux/1/nc/) or
|
||||
[telnet](https://www.unix.com/man-page/linux/1/telnet/).
|
||||
|
||||
* Ask students to visualize what the state of the example server would be with a
|
||||
- Ask students to visualize what the state of the example server would be with a
|
||||
few connected clients. What tasks exist? What are their Futures?
|
||||
|
||||
* This is the first time we've seen an `async` block. This is similar to a
|
||||
- This is the first time we've seen an `async` block. This is similar to a
|
||||
closure, but does not take any arguments. Its return value is a Future,
|
||||
similar to an `async fn`.
|
||||
similar to an `async fn`.
|
||||
|
||||
* Refactor the async block into a function, and improve the error handling using `?`.
|
||||
- Refactor the async block into a function, and improve the error handling using
|
||||
`?`.
|
||||
|
||||
</details>
|
||||
|
Reference in New Issue
Block a user