From bd270fb1109b73282a8bc2c389bf0f104e117af2 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sat, 23 Mar 2024 17:41:17 +0100 Subject: [PATCH 01/16] Create setup pages for the one-day classes --- src/SUMMARY.md | 2 ++ src/android.md | 11 +++++--- src/bare-metal.md | 53 +++++-------------------------------- src/bare-metal/setup.md | 49 ++++++++++++++++++++++++++++++++++ src/chromium.md | 6 +++++ src/concurrency/fearless.md | 14 ++++++++++ src/concurrency/welcome.md | 30 ++++++++++++--------- 7 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 src/bare-metal/setup.md create mode 100644 src/concurrency/fearless.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f563654c6295..b181c87b5407 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -305,6 +305,7 @@ --- - [Welcome](bare-metal.md) +- [Setup](bare-metal/setup.md) - [`no_std`](bare-metal/no_std.md) - [A Minimal Example](bare-metal/minimal.md) - [`alloc`](bare-metal/alloc.md) @@ -356,6 +357,7 @@ --- - [Welcome](concurrency/welcome.md) +- [Fearless Concurrency](concurrency/fearless.md) - [Threads](concurrency/threads.md) - [Plain Threads](concurrency/threads/plain.md) - [Scoped Threads](concurrency/threads/scoped.md) diff --git a/src/android.md b/src/android.md index 82bc767a0806..505eb9e80a45 100644 --- a/src/android.md +++ b/src/android.md @@ -5,9 +5,14 @@ session: Android # Welcome to Rust in Android -Rust is supported for system software on Android. This means that you can write -new services, libraries, drivers or even firmware in Rust (or improve existing -code as needed). +This is a one-day course about Rust in Android: you can write new Android +platform services, libraries, drivers or even firmware in Rust. + +## Target Audience + +This course builds on [Rust Fundamentals](welcome-day-1.md) and we expect you +are familiar with the basics of Rust. You should also be familiar with +development on the Android Platform (AOSP). > We will attempt to call Rust from one of your own projects today. So try to > find a little corner of your code base where we can move some lines of code to diff --git a/src/bare-metal.md b/src/bare-metal.md index a2e45527593e..7636d6e50a94 100644 --- a/src/bare-metal.md +++ b/src/bare-metal.md @@ -5,57 +5,18 @@ session: Morning # Welcome to Bare Metal Rust -This is a standalone one-day course about bare-metal Rust, aimed at people who -are familiar with the basics of Rust (perhaps from completing the Comprehensive -Rust course), and ideally also have some experience with bare-metal programming -in some other language such as C. +This is a one-day course about bare-metal Rust: running Rust code without an OS +underneath us. -Today we will talk about 'bare-metal' Rust: running Rust code without an OS -underneath us. This will be divided into several parts: +The class is divided into several parts: - What is `no_std` Rust? - Writing firmware for microcontrollers. - Writing bootloader / kernel code for application processors. - Some useful crates for bare-metal Rust development. -For the microcontroller part of the course we will use the -[BBC micro:bit](https://microbit.org/) v2 as an example. It's a -[development board](https://tech.microbit.org/hardware/) based on the Nordic -nRF52833 microcontroller with some LEDs and buttons, an I2C-connected -accelerometer and compass, and an on-board SWD debugger. +## Target Audience -To get started, install some tools we'll need later. On gLinux or Debian: - - - -```bash -sudo apt install gcc-aarch64-linux-gnu gdb-multiarch libudev-dev picocom pkg-config qemu-system-arm -rustup update -rustup target add aarch64-unknown-none thumbv7em-none-eabihf -rustup component add llvm-tools-preview -cargo install cargo-binutils cargo-embed -``` - -And give users in the `plugdev` group access to the micro:bit programmer: - - - -```bash -echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0d28", MODE="0664", GROUP="plugdev"' |\ - sudo tee /etc/udev/rules.d/50-microbit.rules -sudo udevadm control --reload-rules -``` - -On MacOS: - - - -```bash -xcode-select --install -brew install gdb picocom qemu -brew install --cask gcc-aarch64-embedded -rustup update -rustup target add aarch64-unknown-none thumbv7em-none-eabihf -rustup component add llvm-tools-preview -cargo install cargo-binutils cargo-embed -``` +This course builds on [Rust Fundamentals](welcome-day-1.md) and we expect you +are familiar with the basics of Rust. You should ideally also have some +experience with bare-metal programming in some other language such as C. diff --git a/src/bare-metal/setup.md b/src/bare-metal/setup.md new file mode 100644 index 000000000000..04298029b8d5 --- /dev/null +++ b/src/bare-metal/setup.md @@ -0,0 +1,49 @@ +# Setup + +For the microcontroller part of the course we will use the +[BBC micro:bit](https://microbit.org/) v2 as an example. It's a +[development board](https://tech.microbit.org/hardware/) based on the Nordic +nRF52833 microcontroller with some LEDs and buttons, an I2C-connected +accelerometer and compass, and an on-board SWD debugger. + +To get started, install some tools we'll need later. + +## Linux + +Please run the following on gLinux or Debian: + + + +```bash +sudo apt install gcc-aarch64-linux-gnu gdb-multiarch libudev-dev picocom pkg-config qemu-system-arm +rustup update +rustup target add aarch64-unknown-none thumbv7em-none-eabihf +rustup component add llvm-tools-preview +cargo install cargo-binutils cargo-embed +``` + +And give users in the `plugdev` group access to the micro:bit programmer: + + + +```bash +echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0d28", MODE="0664", GROUP="plugdev"' |\ + sudo tee /etc/udev/rules.d/50-microbit.rules +sudo udevadm control --reload-rules +``` + +## MacOS + +Please run the following on MacOS: + + + +```bash +xcode-select --install +brew install gdb picocom qemu +brew install --cask gcc-aarch64-embedded +rustup update +rustup target add aarch64-unknown-none thumbv7em-none-eabihf +rustup component add llvm-tools-preview +cargo install cargo-binutils cargo-embed +``` diff --git a/src/chromium.md b/src/chromium.md index 442db003b900..4da37cc5bfe9 100644 --- a/src/chromium.md +++ b/src/chromium.md @@ -12,3 +12,9 @@ code to connect between Rust and existing Chromium C++ code. > a corner of the code where you're displaying a UTF8 string to the user, feel > free to follow this recipe in your part of the codebase instead of the exact > part we talk about. + +## Target Audience + +This course builds on [Rust Fundamentals](welcome-day-1.md) and we expect you +are familiar with the basics of Rust. You should also be familiar with Chromium +development. diff --git a/src/concurrency/fearless.md b/src/concurrency/fearless.md new file mode 100644 index 000000000000..90919021a667 --- /dev/null +++ b/src/concurrency/fearless.md @@ -0,0 +1,14 @@ +# Fearless Concurrency + +Rust has great support for concurrency and its powerful type system is able to +prevent many concurrency bugs at compile time. This is often referred to as +_fearless concurrency_ since you can rely on the compiler to ensure correctness +at runtime. + +
+ +- Rust lets us access OS concurrency primitives such as threads and mutexes. +- We will see how the type system gives prevents certain kinds of concurrency + bugs when using multiple threads. + +
diff --git a/src/concurrency/welcome.md b/src/concurrency/welcome.md index 0a35006d02dd..60d5bb32814a 100644 --- a/src/concurrency/welcome.md +++ b/src/concurrency/welcome.md @@ -6,23 +6,29 @@ target_minutes: 180 # Welcome to Concurrency in Rust -Rust has full support for concurrency using OS threads with mutexes and -channels. +This is one-day course about concurrency in Rust: structuring your program so it +does multiple things concurrently or in parallel. -The Rust type system plays an important role in making many concurrency bugs -compile time bugs. This is often referred to as _fearless concurrency_ since you -can rely on the compiler to ensure correctness at runtime. +We will cover two major parts of Rust today: + +- Multi-threaded programming using threads and mutexes. +- Concurrent programming using the `async` keyword. ## Schedule {{%session outline}} -
+## Target Audience -- Rust lets us access OS concurrency toolkit: threads, sync. primitives, etc. -- The type system gives us safety for concurrency without any special features. -- The same tools that help with "concurrent" access in a single thread (e.g., a - called function that might mutate an argument or save references to it to read - later) save us from multi-threading issues. +This course builds on [Rust Fundamentals](../welcome-day-1.md). To get the most +out of the class, we expect that you are familiar with the basics of Rust, as +well as concepts such as: -
+- [Borrowing](../borrowing.md): you should understand the difference between + shared borrows (`&T`) and exclusive borrows (`&mut T`), +- [Generics](../generics.md): we will use a lot of + [trait bounds](../generics/trait-bounds.md). +- [Closures](../std-traits/closures.md): make sure you understand how closures + capture values from their environment. +- [`Rc`](../smart-pointers/rc.md): we will use a similar type for shared + ownership. From 1a86faf8ab0056ee18b61f2b191963702e4a322a Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 24 Mar 2024 11:16:34 +0000 Subject: [PATCH 02/16] Links in channel pages --- src/concurrency/channels/bounded.md | 13 ++++++++----- src/concurrency/channels/senders-receivers.md | 14 ++++++++++---- src/concurrency/channels/unbounded.md | 4 +++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/concurrency/channels/bounded.md b/src/concurrency/channels/bounded.md index e1667d4f54ec..b1500b34c0e4 100644 --- a/src/concurrency/channels/bounded.md +++ b/src/concurrency/channels/bounded.md @@ -4,7 +4,7 @@ minutes: 8 # Bounded Channels -With bounded (synchronous) channels, `send` can block the current thread: +With bounded (synchronous) channels, [`send()`] can block the current thread: ```rust,editable use std::sync::mpsc; @@ -32,12 +32,15 @@ fn main() {
-- Calling `send` will block the current thread until there is space in the +- Calling `send()` will block the current thread until there is space in the channel for the new message. The thread can be blocked indefinitely if there is nobody who reads from the channel. -- A call to `send` will abort with an error (that is why it returns `Result`) if - the channel is closed. A channel is closed when the receiver is dropped. +- A call to `send()` will abort with an error (that is why it returns `Result`) + if the channel is closed. A channel is closed when the receiver is dropped. - A bounded channel with a size of zero is called a "rendezvous channel". Every - send will block the current thread until another thread calls `recv`. + send will block the current thread until another thread calls [`recv()`].
+ +[`send()`]: https://doc.rust-lang.org/std/sync/mpsc/struct.SyncSender.html#method.send +[`recv()`]: https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html#method.recv diff --git a/src/concurrency/channels/senders-receivers.md b/src/concurrency/channels/senders-receivers.md index c0ece3576a65..2609196efd73 100644 --- a/src/concurrency/channels/senders-receivers.md +++ b/src/concurrency/channels/senders-receivers.md @@ -4,8 +4,8 @@ minutes: 9 # Senders and Receivers -Rust channels have two parts: a `Sender` and a `Receiver`. The two parts -are connected via the channel, but you only see the end-points. +Rust channels have two parts: a [`Sender`] and a [`Receiver`]. The two +parts are connected via the channel, but you only see the end-points. ```rust,editable use std::sync::mpsc; @@ -27,10 +27,16 @@ fn main() {
-- `mpsc` stands for Multi-Producer, Single-Consumer. `Sender` and `SyncSender` +- [`mpsc`] stands for Multi-Producer, Single-Consumer. `Sender` and `SyncSender` implement `Clone` (so you can make multiple producers) but `Receiver` does not. -- `send()` and `recv()` return `Result`. If they return `Err`, it means the +- [`send()`] and [`recv()`] return `Result`. If they return `Err`, it means the counterpart `Sender` or `Receiver` is dropped and the channel is closed.
+ +[`Sender`]: https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html +[`Receiver`]: https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html +[`send()`]: https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html#method.send +[`recv()`]: https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html#method.recv +[`mpsc`]: https://doc.rust-lang.org/std/sync/mpsc/index.html diff --git a/src/concurrency/channels/unbounded.md b/src/concurrency/channels/unbounded.md index c06641218ea1..13008c842163 100644 --- a/src/concurrency/channels/unbounded.md +++ b/src/concurrency/channels/unbounded.md @@ -4,7 +4,7 @@ minutes: 2 # Unbounded Channels -You get an unbounded and asynchronous channel with `mpsc::channel()`: +You get an unbounded and asynchronous channel with [`mpsc::channel()`]: ```rust,editable use std::sync::mpsc; @@ -29,3 +29,5 @@ fn main() { } } ``` + +[`mpsc::channel()`]: https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html From ee1bdf39aa20045a2e0dc606150dd46d96733c3b Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 24 Mar 2024 11:16:45 +0000 Subject: [PATCH 03/16] Speaker notes for unbounded channels --- src/concurrency/channels/unbounded.md | 13 +++++++++++++ src/smart-pointers/rc.md | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/concurrency/channels/unbounded.md b/src/concurrency/channels/unbounded.md index 13008c842163..c02d5a8f55a1 100644 --- a/src/concurrency/channels/unbounded.md +++ b/src/concurrency/channels/unbounded.md @@ -30,4 +30,17 @@ fn main() { } ``` +
+ +- The channel is called asynchronous because there is no synchronization between + sending and receiving. +- The channel buffers the values. The buffer grows automatically, similar to how + a `Vec` grows when you push data to it. +- The channel takes ownership of the values when you call [`send()`]. This is + seen in the signature: it takes `T` by value. You thus lose access to the + value you send into a channel. + +
+ [`mpsc::channel()`]: https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html +[`send()`]: https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html#method.send diff --git a/src/smart-pointers/rc.md b/src/smart-pointers/rc.md index 6b55d5f4fd31..525b3e6689b1 100644 --- a/src/smart-pointers/rc.md +++ b/src/smart-pointers/rc.md @@ -24,7 +24,7 @@ fn main() { cycles that will get dropped. [1]: https://doc.rust-lang.org/std/rc/struct.Rc.html -[2]: ../concurrency/shared_state/arc.md +[2]: ../concurrency/shared-state/arc.md [3]: https://doc.rust-lang.org/std/sync/struct.Mutex.html [4]: https://doc.rust-lang.org/std/rc/struct.Weak.html From 62f426c878d14ef6c859a3394e7702df8ae6ad52 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 24 Mar 2024 11:54:15 +0000 Subject: [PATCH 04/16] Align smart pointer descriptions and links We now avoid the `` in the page title. We refer to `Rc` and `Arc` as reference-counted shared pointers that allow access to data from multiple places or threads. --- src/SUMMARY.md | 2 +- src/borrowing/interior-mutability.md | 11 ++++++++--- src/concurrency/shared-state/arc.md | 16 ++++++++++------ src/concurrency/shared-state/mutex.md | 2 +- src/smart-pointers/box.md | 9 ++++----- src/smart-pointers/rc.md | 6 +++--- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index b181c87b5407..5c0066add914 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -140,7 +140,7 @@ - [Exercise: Builder Type](memory-management/exercise.md) - [Solution](memory-management/solution.md) - [Smart Pointers](smart-pointers.md) - - [`Box`](smart-pointers/box.md) + - [`Box`](smart-pointers/box.md) - [`Rc`](smart-pointers/rc.md) - [Owned Trait Objects](smart-pointers/trait-objects.md) - [Exercise: Binary Tree](smart-pointers/exercise.md) diff --git a/src/borrowing/interior-mutability.md b/src/borrowing/interior-mutability.md index def874e9c560..4fc543e78801 100644 --- a/src/borrowing/interior-mutability.md +++ b/src/borrowing/interior-mutability.md @@ -14,6 +14,8 @@ while still ensuring safety, typically by performing a runtime check. ## `RefCell` +A [`RefCell`] gives you mutable access to a value behind a shared reference: + ```rust,editable use std::cell::RefCell; @@ -36,9 +38,9 @@ fn main() { ## `Cell` -`Cell` wraps a value and allows getting or setting the value, even with a shared -reference to the `Cell`. However, it does not allow any references to the value. -Since there are no references, borrowing rules cannot be broken. +[`Cell`] wraps a `T` value. It allows getting or setting the value, even with +a shared reference to the `Cell`. However, it does not allow any references to +the value. Since there are no references, the borrowing rules cannot be broken. ```rust,editable use std::cell::Cell; @@ -72,3 +74,6 @@ that safety, and `RefCell` and `Cell` are two of them. have its own cost. + +[`Cell`]: https://doc.rust-lang.org/std/cell/struct.Cell.html +[`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html diff --git a/src/concurrency/shared-state/arc.md b/src/concurrency/shared-state/arc.md index 694a6dc29d4c..424f846ff77c 100644 --- a/src/concurrency/shared-state/arc.md +++ b/src/concurrency/shared-state/arc.md @@ -4,7 +4,8 @@ minutes: 5 # `Arc` -[`Arc`][1] allows shared read-only access via `Arc::clone`: +[`Arc`] is a reference-counted shared pointer to `T`. Use this when you need +to refer to the same data from multiple threads: ```rust,editable use std::sync::Arc; @@ -26,18 +27,21 @@ fn main() { } ``` -[1]: https://doc.rust-lang.org/std/sync/struct.Arc.html -
-- `Arc` stands for "Atomic Reference Counted", a thread safe version of `Rc` - that uses atomic operations. +- `Arc` is short for "Atomically Reference Counted". It uses atomic + (thread-safe) CPU instructions to maintain the reference count. `Arc` is a + thread safe version of [`Rc`]. - `Arc` implements `Clone` whether or not `T` does. It implements `Send` and `Sync` if and only if `T` implements them both. - `Arc::clone()` has the cost of atomic operations that get executed, but after that the use of the `T` is free. - Beware of reference cycles, `Arc` does not use a garbage collector to detect them. - - `std::sync::Weak` can help. + - [`std::sync::Weak`] can help avoid reference cycles.
+ +[`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html +[`Rc`]: ../../smart-pointers/rc.md +[`std::sync::Weak`]: https://doc.rust-lang.org/std/sync/struct.Weak.html diff --git a/src/concurrency/shared-state/mutex.md b/src/concurrency/shared-state/mutex.md index 555ee55f8a97..57721a18b747 100644 --- a/src/concurrency/shared-state/mutex.md +++ b/src/concurrency/shared-state/mutex.md @@ -6,7 +6,7 @@ minutes: 14 [`Mutex`][1] ensures mutual exclusion _and_ allows mutable access to `T` behind a read-only interface (another form of -[interior mutability](../../borrowing/interior-mutability)): +[interior mutability](../../borrowing/interior-mutability.md)): ```rust,editable use std::sync::Mutex; diff --git a/src/smart-pointers/box.md b/src/smart-pointers/box.md index dbf91cd0e328..bd6b247677d4 100644 --- a/src/smart-pointers/box.md +++ b/src/smart-pointers/box.md @@ -2,10 +2,10 @@ minutes: 8 --- -# `Box` +# `Box` -[`Box`](https://doc.rust-lang.org/std/boxed/struct.Box.html) is an owned pointer -to data on the heap: +[`Box`](https://doc.rust-lang.org/std/boxed/struct.Box.html) is an owned +pointer to a `T` on the heap: ```rust,editable fn main() { @@ -28,8 +28,7 @@ fn main() { ``` `Box` implements `Deref`, which means that you can -[call methods -from `T` directly on a `Box`](https://doc.rust-lang.org/std/ops/trait.Deref.html#more-on-deref-coercion). +[call methods from `T` directly](https://doc.rust-lang.org/std/ops/trait.Deref.html#more-on-deref-coercion). Recursive data types or data types with dynamic sizes need to use a `Box`: diff --git a/src/smart-pointers/rc.md b/src/smart-pointers/rc.md index 525b3e6689b1..804f6fb5fea3 100644 --- a/src/smart-pointers/rc.md +++ b/src/smart-pointers/rc.md @@ -4,8 +4,8 @@ minutes: 5 # `Rc` -[`Rc`][1] is a reference-counted shared pointer. Use this when you need to refer -to the same data from multiple places: +[`Rc`][1] is a reference-counted shared pointer to `T`. Use this when you +need to refer to the same data from multiple places: ```rust,editable use std::rc::Rc; @@ -25,7 +25,7 @@ fn main() { [1]: https://doc.rust-lang.org/std/rc/struct.Rc.html [2]: ../concurrency/shared-state/arc.md -[3]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +[3]: ../concurrency/shared-state/mutex.md [4]: https://doc.rust-lang.org/std/rc/struct.Weak.html
From 71b6d5ca2076e14d81288179262c4ad227de8a8f Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 24 Mar 2024 12:07:37 +0000 Subject: [PATCH 05/16] Link more consistently to stdlib on mutex page --- src/concurrency/shared-state/mutex.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/concurrency/shared-state/mutex.md b/src/concurrency/shared-state/mutex.md index 57721a18b747..6cefc9da10d0 100644 --- a/src/concurrency/shared-state/mutex.md +++ b/src/concurrency/shared-state/mutex.md @@ -4,8 +4,8 @@ minutes: 14 # `Mutex` -[`Mutex`][1] ensures mutual exclusion _and_ allows mutable access to `T` -behind a read-only interface (another form of +[`Mutex`] ensures mutual exclusion _and_ allows mutable access to `T` behind +a read-only interface (another form of [interior mutability](../../borrowing/interior-mutability.md)): ```rust,editable @@ -27,9 +27,7 @@ fn main() { Notice how we have a [`impl Sync for Mutex`][2] blanket implementation. -[1]: https://doc.rust-lang.org/std/sync/struct.Mutex.html [2]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#impl-Sync-for-Mutex%3CT%3E -[3]: https://doc.rust-lang.org/std/sync/struct.Arc.html
@@ -41,13 +39,15 @@ implementation. `MutexGuard` ensures that the `&mut T` doesn't outlive the lock being held. - `Mutex` implements both `Send` and `Sync` iff (if and only if) `T` implements `Send`. -- A read-write lock counterpart: `RwLock`. +- Rust has a multi-reader single-writer lock counterpart: [`RwLock`]. - Why does `lock()` return a `Result`? - If the thread that held the `Mutex` panicked, the `Mutex` becomes "poisoned" to signal that the data it protected might be in an inconsistent state. Calling `lock()` on a poisoned mutex fails with a [`PoisonError`]. You can call `into_inner()` on the error to recover the data regardless. -[`PoisonError`]: https://doc.rust-lang.org/std/sync/struct.PoisonError.html -
+ +[`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +[`RwLock`]: https://doc.rust-lang.org/std/sync/struct.RwLock.html +[`PoisonError`]: https://doc.rust-lang.org/std/sync/struct.PoisonError.html From 0ffcb4878445fca268125d650fa99b0008705e75 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 24 Mar 2024 20:23:35 +0000 Subject: [PATCH 06/16] Simplify language, add links, formatting fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Avoid time-specific statements (“recently”, “today”, …). - Move details such as “RPIT” to the speaker notes. - More links to our slides as well as community documentation. - Formatting fixes --- .../async-pitfalls/async-traits.md | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/concurrency/async-pitfalls/async-traits.md b/src/concurrency/async-pitfalls/async-traits.md index 5b285ec1780b..8d5520527c6f 100644 --- a/src/concurrency/async-pitfalls/async-traits.md +++ b/src/concurrency/async-pitfalls/async-traits.md @@ -4,22 +4,20 @@ minutes: 5 # Async Traits -Async methods in traits are were stabilized only recently, in the 1.75 release. -This required support for using return-position `impl Trait` (RPIT) in traits, -as the desugaring for `async fn` includes `-> impl Future`. +Async methods in traits are were stabilized in the 1.75 release. This required +support for using return-position `impl Trait` in traits, as the desugaring for +`async fn` includes `-> impl Future`. -However, even with the native support today there are some pitfalls around -`async fn` and RPIT in traits: +However, even with the native support, there are some pitfalls around +`async fn`: -- Return-position impl Trait captures all in-scope lifetimes (so some patterns - of borrowing cannot be expressed) +- Return-position `impl Trait` captures all in-scope lifetimes (so some patterns + of borrowing cannot be expressed). -- Traits whose methods use return-position `impl trait` or `async` are not `dyn` - compatible. +- Async traits cannot be used with [trait objects] (`dyn Trait` support). -If we do need `dyn` support, the crate -[async_trait](https://docs.rs/async-trait/latest/async_trait/) provides a -workaround through a macro, with some caveats: +The [async_trait] crate provides a workaround for `dyn` support through a macro, +with some caveats: ```rust,editable,compile_fail use async_trait::async_trait; @@ -47,11 +45,11 @@ async fn run_all_sleepers_multiple_times( n_times: usize, ) { for _ in 0..n_times { - println!("running all sleepers.."); + println!("Running all sleepers..."); for sleeper in &sleepers { let start = Instant::now(); sleeper.sleep().await; - println!("slept for {}ms", start.elapsed().as_millis()); + println!("Slept for {} ms", start.elapsed().as_millis()); } } } @@ -71,13 +69,21 @@ async fn main() { - `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 - 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/) - if you are interested in digging deeper. +- The challenges in language support for `async trait` are too deep to describe + in-depth in this class. See [this blog post] by Niko Matsakis if you are + interested in digging deeper. See also these keywords: + + - [RPIT]: short for + [return-position `impl Trait`](../../generics/impl-trait.md). + - [RPITIT]: short for return-position `impl Trait` in trait (RPIT in trait). - Try creating a new sleeper struct that will sleep for a random amount of time - and adding it to the Vec. + and adding it to the `Vec`.
+ +[async_trait]: https://docs.rs/async-trait/ +[trait objects]: ../../smart-pointers/trait-objects.md +[this blog post]: https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/ +[RPIT]: https://doc.rust-lang.org/reference/types/impl-trait.html#abstract-return-types +[RPITIT]: https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html From ba7821b2c26cafe68170d93d1556dad1064ddb4b Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 24 Mar 2024 20:46:03 +0000 Subject: [PATCH 07/16] Make async code testable This moves all non-trivial examples in the async part of the course to self-contained Rust files. This ensures that we can test them in CI. --- Cargo.lock | 62 ++++++++++++++++++- Cargo.toml | 3 + src/concurrency/async-control-flow/Cargo.toml | 24 +++++++ .../async-control-flow/channels.md | 26 +------- .../async-control-flow/channels.rs | 25 ++++++++ src/concurrency/async-control-flow/join.md | 25 +------- src/concurrency/async-control-flow/join.rs | 24 +++++++ src/concurrency/async-control-flow/select.md | 39 +----------- src/concurrency/async-control-flow/select.rs | 38 ++++++++++++ src/concurrency/async-pitfalls/Cargo.toml | 24 +++++++ .../async-pitfalls/async-traits.md | 43 +------------ .../async-pitfalls/async-traits.rs | 42 +++++++++++++ .../async-pitfalls/cancellation.md | 60 +----------------- .../async-pitfalls/cancellation.rs | 59 ++++++++++++++++++ src/concurrency/async-pitfalls/pin.md | 49 +-------------- src/concurrency/async-pitfalls/pin.rs | 48 ++++++++++++++ src/concurrency/async/Cargo.toml | 16 +++++ src/concurrency/async/tasks.md | 25 +------- src/concurrency/async/tasks.rs | 24 +++++++ 19 files changed, 394 insertions(+), 262 deletions(-) create mode 100644 src/concurrency/async-control-flow/Cargo.toml create mode 100644 src/concurrency/async-control-flow/channels.rs create mode 100644 src/concurrency/async-control-flow/join.rs create mode 100644 src/concurrency/async-control-flow/select.rs create mode 100644 src/concurrency/async-pitfalls/Cargo.toml create mode 100644 src/concurrency/async-pitfalls/async-traits.rs create mode 100644 src/concurrency/async-pitfalls/cancellation.rs create mode 100644 src/concurrency/async-pitfalls/pin.rs create mode 100644 src/concurrency/async/Cargo.toml create mode 100644 src/concurrency/async/tasks.rs diff --git a/Cargo.lock b/Cargo.lock index e4848d43069e..3154677fcd09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,50 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "async" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "reqwest", + "tokio", +] + +[[package]] +name = "async-control-flow" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "reqwest", + "tokio", +] + +[[package]] +name = "async-pitfalls" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "reqwest", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -703,11 +747,25 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", diff --git a/Cargo.toml b/Cargo.toml index 3a12574a013c..b33c0d92e2b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ members = [ "src/std-traits", "src/std-types", "src/testing", + "src/concurrency/async", + "src/concurrency/async-control-flow", + "src/concurrency/async-pitfalls", "src/tuples-and-arrays", "src/types-and-values", "src/unsafe-rust", diff --git a/src/concurrency/async-control-flow/Cargo.toml b/src/concurrency/async-control-flow/Cargo.toml new file mode 100644 index 000000000000..a0b134130b6d --- /dev/null +++ b/src/concurrency/async-control-flow/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "async-control-flow" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "channels" +path = "channels.rs" + +[[bin]] +name = "join" +path = "join.rs" + +[[bin]] +name = "select" +path = "select.rs" + +[dependencies] +anyhow = "1.0.81" +async-trait = "0.1.79" +futures = { version = "0.3.30", default-features = false } +reqwest = { version = "0.12.1", default-features = false } +tokio = { version = "1.36.0", features = ["full"] } diff --git a/src/concurrency/async-control-flow/channels.md b/src/concurrency/async-control-flow/channels.md index 72678464be67..497b69f5b833 100644 --- a/src/concurrency/async-control-flow/channels.md +++ b/src/concurrency/async-control-flow/channels.md @@ -7,31 +7,7 @@ minutes: 8 Several crates have support for asynchronous channels. For instance `tokio`: ```rust,editable,compile_fail -use tokio::sync::mpsc::{self, Receiver}; - -async fn ping_handler(mut input: Receiver<()>) { - let mut count: usize = 0; - - while let Some(_) = input.recv().await { - count += 1; - println!("Received {count} pings so far."); - } - - println!("ping_handler complete"); -} - -#[tokio::main] -async fn main() { - let (sender, receiver) = mpsc::channel(32); - let ping_handler_task = tokio::spawn(ping_handler(receiver)); - for i in 0..10 { - sender.send(()).await.expect("Failed to send ping."); - println!("Sent {} pings so far.", i + 1); - } - - drop(sender); - ping_handler_task.await.expect("Something went wrong in ping handler task."); -} +{{#include channels.rs}} ```
diff --git a/src/concurrency/async-control-flow/channels.rs b/src/concurrency/async-control-flow/channels.rs new file mode 100644 index 000000000000..3aefa055caad --- /dev/null +++ b/src/concurrency/async-control-flow/channels.rs @@ -0,0 +1,25 @@ +use tokio::sync::mpsc::{self, Receiver}; + +async fn ping_handler(mut input: Receiver<()>) { + let mut count: usize = 0; + + while let Some(_) = input.recv().await { + count += 1; + println!("Received {count} pings so far."); + } + + println!("ping_handler complete"); +} + +#[tokio::main] +async fn main() { + let (sender, receiver) = mpsc::channel(32); + let ping_handler_task = tokio::spawn(ping_handler(receiver)); + for i in 0..10 { + sender.send(()).await.expect("Failed to send ping."); + println!("Sent {} pings so far.", i + 1); + } + + drop(sender); + ping_handler_task.await.expect("Something went wrong in ping handler task."); +} diff --git a/src/concurrency/async-control-flow/join.md b/src/concurrency/async-control-flow/join.md index 6aa06e6d0d23..5962120aac87 100644 --- a/src/concurrency/async-control-flow/join.md +++ b/src/concurrency/async-control-flow/join.md @@ -9,30 +9,7 @@ collection of their results. This is similar to `Promise.all` in JavaScript or `asyncio.gather` in Python. ```rust,editable,compile_fail -use anyhow::Result; -use futures::future; -use reqwest; -use std::collections::HashMap; - -async fn size_of_page(url: &str) -> Result { - let resp = reqwest::get(url).await?; - Ok(resp.text().await?.len()) -} - -#[tokio::main] -async fn main() { - let urls: [&str; 4] = [ - "https://google.com", - "https://httpbin.org/ip", - "https://play.rust-lang.org/", - "BAD_URL", - ]; - let futures_iter = urls.into_iter().map(size_of_page); - let results = future::join_all(futures_iter).await; - let page_sizes_dict: HashMap<&str, Result> = - urls.into_iter().zip(results.into_iter()).collect(); - println!("{:?}", page_sizes_dict); -} +{{#include join.rs}} ```
diff --git a/src/concurrency/async-control-flow/join.rs b/src/concurrency/async-control-flow/join.rs new file mode 100644 index 000000000000..2fae1c87ebbe --- /dev/null +++ b/src/concurrency/async-control-flow/join.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use futures::future; +use reqwest; +use std::collections::HashMap; + +async fn size_of_page(url: &str) -> Result { + let resp = reqwest::get(url).await?; + Ok(resp.text().await?.len()) +} + +#[tokio::main] +async fn main() { + let urls: [&str; 4] = [ + "https://google.com", + "https://httpbin.org/ip", + "https://play.rust-lang.org/", + "BAD_URL", + ]; + let futures_iter = urls.into_iter().map(size_of_page); + let results = future::join_all(futures_iter).await; + let page_sizes_dict: HashMap<&str, Result> = + urls.into_iter().zip(results.into_iter()).collect(); + println!("{:?}", page_sizes_dict); +} diff --git a/src/concurrency/async-control-flow/select.md b/src/concurrency/async-control-flow/select.md index b3df5c2f8539..257d37be572e 100644 --- a/src/concurrency/async-control-flow/select.md +++ b/src/concurrency/async-control-flow/select.md @@ -16,44 +16,7 @@ the resulting variables. The `statement` result becomes the result of the `select!` macro. ```rust,editable,compile_fail -use tokio::sync::mpsc::{self, Receiver}; -use tokio::time::{sleep, Duration}; - -#[derive(Debug, PartialEq)] -enum Animal { - Cat { name: String }, - Dog { name: String }, -} - -async fn first_animal_to_finish_race( - mut cat_rcv: Receiver, - mut dog_rcv: Receiver, -) -> Option { - tokio::select! { - cat_name = cat_rcv.recv() => Some(Animal::Cat { name: cat_name? }), - dog_name = dog_rcv.recv() => Some(Animal::Dog { name: dog_name? }) - } -} - -#[tokio::main] -async fn main() { - let (cat_sender, cat_receiver) = mpsc::channel(32); - 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."); - }); - tokio::spawn(async move { - sleep(Duration::from_millis(50)).await; - dog_sender.send(String::from("Rex")).await.expect("Failed to send dog."); - }); - - let winner = first_animal_to_finish_race(cat_receiver, dog_receiver) - .await - .expect("Failed to receive winner"); - - println!("Winner is {winner:?}"); -} +{{#include select.rs}} ```
diff --git a/src/concurrency/async-control-flow/select.rs b/src/concurrency/async-control-flow/select.rs new file mode 100644 index 000000000000..69f2e8d3fd03 --- /dev/null +++ b/src/concurrency/async-control-flow/select.rs @@ -0,0 +1,38 @@ +use tokio::sync::mpsc::{self, Receiver}; +use tokio::time::{sleep, Duration}; + +#[derive(Debug, PartialEq)] +enum Animal { + Cat { name: String }, + Dog { name: String }, +} + +async fn first_animal_to_finish_race( + mut cat_rcv: Receiver, + mut dog_rcv: Receiver, +) -> Option { + tokio::select! { + cat_name = cat_rcv.recv() => Some(Animal::Cat { name: cat_name? }), + dog_name = dog_rcv.recv() => Some(Animal::Dog { name: dog_name? }) + } +} + +#[tokio::main] +async fn main() { + let (cat_sender, cat_receiver) = mpsc::channel(32); + 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."); + }); + tokio::spawn(async move { + sleep(Duration::from_millis(50)).await; + dog_sender.send(String::from("Rex")).await.expect("Failed to send dog."); + }); + + let winner = first_animal_to_finish_race(cat_receiver, dog_receiver) + .await + .expect("Failed to receive winner"); + + println!("Winner is {winner:?}"); +} diff --git a/src/concurrency/async-pitfalls/Cargo.toml b/src/concurrency/async-pitfalls/Cargo.toml new file mode 100644 index 000000000000..a14bb6d504fb --- /dev/null +++ b/src/concurrency/async-pitfalls/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "async-pitfalls" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "async-traits" +path = "async-traits.rs" + +[[bin]] +name = "cancellation" +path = "cancellation.rs" + +[[bin]] +name = "pin" +path = "pin.rs" + +[dependencies] +anyhow = "1.0.81" +async-trait = "0.1.79" +futures = { version = "0.3.30", default-features = false } +reqwest = { version = "0.12.1", default-features = false } +tokio = { version = "1.36.0", features = ["full"] } diff --git a/src/concurrency/async-pitfalls/async-traits.md b/src/concurrency/async-pitfalls/async-traits.md index 8d5520527c6f..00140affcafa 100644 --- a/src/concurrency/async-pitfalls/async-traits.md +++ b/src/concurrency/async-pitfalls/async-traits.md @@ -20,48 +20,7 @@ The [async_trait] crate provides a workaround for `dyn` support through a macro, with some caveats: ```rust,editable,compile_fail -use async_trait::async_trait; -use std::time::Instant; -use tokio::time::{sleep, Duration}; - -#[async_trait] -trait Sleeper { - async fn sleep(&self); -} - -struct FixedSleeper { - sleep_ms: u64, -} - -#[async_trait] -impl Sleeper for FixedSleeper { - async fn sleep(&self) { - sleep(Duration::from_millis(self.sleep_ms)).await; - } -} - -async fn run_all_sleepers_multiple_times( - sleepers: Vec>, - n_times: usize, -) { - for _ in 0..n_times { - println!("Running all sleepers..."); - for sleeper in &sleepers { - let start = Instant::now(); - sleeper.sleep().await; - println!("Slept for {} ms", start.elapsed().as_millis()); - } - } -} - -#[tokio::main] -async fn main() { - let sleepers: Vec> = vec![ - Box::new(FixedSleeper { sleep_ms: 50 }), - Box::new(FixedSleeper { sleep_ms: 100 }), - ]; - run_all_sleepers_multiple_times(sleepers, 5).await; -} +{{#include async-traits.rs}} ```
diff --git a/src/concurrency/async-pitfalls/async-traits.rs b/src/concurrency/async-pitfalls/async-traits.rs new file mode 100644 index 000000000000..af50c89b9a24 --- /dev/null +++ b/src/concurrency/async-pitfalls/async-traits.rs @@ -0,0 +1,42 @@ +use async_trait::async_trait; +use std::time::Instant; +use tokio::time::{sleep, Duration}; + +#[async_trait] +trait Sleeper { + async fn sleep(&self); +} + +struct FixedSleeper { + sleep_ms: u64, +} + +#[async_trait] +impl Sleeper for FixedSleeper { + async fn sleep(&self) { + sleep(Duration::from_millis(self.sleep_ms)).await; + } +} + +async fn run_all_sleepers_multiple_times( + sleepers: Vec>, + n_times: usize, +) { + for _ in 0..n_times { + println!("Running all sleepers..."); + for sleeper in &sleepers { + let start = Instant::now(); + sleeper.sleep().await; + println!("Slept for {} ms", start.elapsed().as_millis()); + } + } +} + +#[tokio::main] +async fn main() { + let sleepers: Vec> = vec![ + Box::new(FixedSleeper { sleep_ms: 50 }), + Box::new(FixedSleeper { sleep_ms: 100 }), + ]; + run_all_sleepers_multiple_times(sleepers, 5).await; +} diff --git a/src/concurrency/async-pitfalls/cancellation.md b/src/concurrency/async-pitfalls/cancellation.md index 66fae4e6e429..adb258ab17da 100644 --- a/src/concurrency/async-pitfalls/cancellation.md +++ b/src/concurrency/async-pitfalls/cancellation.md @@ -10,65 +10,7 @@ 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}; -use std::time::Duration; -use tokio::io::{AsyncReadExt, AsyncWriteExt, DuplexStream}; - -struct LinesReader { - stream: DuplexStream, -} - -impl LinesReader { - fn new(stream: DuplexStream) -> Self { - Self { stream } - } - - async fn next(&mut self) -> io::Result> { - let mut bytes = Vec::new(); - let mut buf = [0]; - while self.stream.read(&mut buf[..]).await? != 0 { - bytes.push(buf[0]); - if buf[0] == b'\n' { - break; - } - } - if bytes.is_empty() { - return Ok(None); - } - let s = String::from_utf8(bytes) - .map_err(|_| io::Error::new(ErrorKind::InvalidData, "not UTF-8"))?; - Ok(Some(s)) - } -} - -async fn slow_copy(source: String, mut dest: DuplexStream) -> std::io::Result<()> { - for b in source.bytes() { - dest.write_u8(b).await?; - tokio::time::sleep(Duration::from_millis(10)).await - } - Ok(()) -} - -#[tokio::main] -async fn main() -> std::io::Result<()> { - let (client, server) = tokio::io::duplex(5); - let handle = tokio::spawn(slow_copy("hi\nthere\n".to_owned(), client)); - - let mut lines = LinesReader::new(server); - let mut interval = tokio::time::interval(Duration::from_millis(60)); - loop { - tokio::select! { - _ = interval.tick() => println!("tick!"), - line = lines.next() => if let Some(l) = line? { - print!("{}", l) - } else { - break - }, - } - } - handle.await.unwrap()?; - Ok(()) -} +{{#include cancellation.rs}} ```
diff --git a/src/concurrency/async-pitfalls/cancellation.rs b/src/concurrency/async-pitfalls/cancellation.rs new file mode 100644 index 000000000000..d771e6ee2cdf --- /dev/null +++ b/src/concurrency/async-pitfalls/cancellation.rs @@ -0,0 +1,59 @@ +use std::io::{self, ErrorKind}; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt, DuplexStream}; + +struct LinesReader { + stream: DuplexStream, +} + +impl LinesReader { + fn new(stream: DuplexStream) -> Self { + Self { stream } + } + + async fn next(&mut self) -> io::Result> { + let mut bytes = Vec::new(); + let mut buf = [0]; + while self.stream.read(&mut buf[..]).await? != 0 { + bytes.push(buf[0]); + if buf[0] == b'\n' { + break; + } + } + if bytes.is_empty() { + return Ok(None); + } + let s = String::from_utf8(bytes) + .map_err(|_| io::Error::new(ErrorKind::InvalidData, "not UTF-8"))?; + Ok(Some(s)) + } +} + +async fn slow_copy(source: String, mut dest: DuplexStream) -> std::io::Result<()> { + for b in source.bytes() { + dest.write_u8(b).await?; + tokio::time::sleep(Duration::from_millis(10)).await + } + Ok(()) +} + +#[tokio::main] +async fn main() -> std::io::Result<()> { + let (client, server) = tokio::io::duplex(5); + let handle = tokio::spawn(slow_copy("hi\nthere\n".to_owned(), client)); + + let mut lines = LinesReader::new(server); + let mut interval = tokio::time::interval(Duration::from_millis(60)); + loop { + tokio::select! { + _ = interval.tick() => println!("tick!"), + line = lines.next() => if let Some(l) = line? { + print!("{}", l) + } else { + break + }, + } + } + handle.await.unwrap()?; + Ok(()) +} diff --git a/src/concurrency/async-pitfalls/pin.md b/src/concurrency/async-pitfalls/pin.md index fc764a8af083..85be24acae45 100644 --- a/src/concurrency/async-pitfalls/pin.md +++ b/src/concurrency/async-pitfalls/pin.md @@ -18,54 +18,7 @@ operations that would move the instance it points to into a different memory location. ```rust,editable,compile_fail -use tokio::sync::{mpsc, oneshot}; -use tokio::task::spawn; -use tokio::time::{sleep, Duration}; - -// A work item. In this case, just sleep for the given time and respond -// with a message on the `respond_on` channel. -#[derive(Debug)] -struct Work { - input: u32, - respond_on: oneshot::Sender, -} - -// A worker which listens for work on a queue and performs it. -async fn worker(mut work_queue: mpsc::Receiver) { - let mut iterations = 0; - loop { - tokio::select! { - Some(work) = work_queue.recv() => { - sleep(Duration::from_millis(10)).await; // Pretend to work. - work.respond_on - .send(work.input * 1000) - .expect("failed to send response"); - iterations += 1; - } - // TODO: report number of iterations every 100ms - } - } -} - -// A requester which requests work and waits for it to complete. -async fn do_work(work_queue: &mpsc::Sender, input: u32) -> u32 { - let (tx, rx) = oneshot::channel(); - work_queue - .send(Work { input, respond_on: tx }) - .await - .expect("failed to send on work queue"); - rx.await.expect("failed waiting for response") -} - -#[tokio::main] -async fn main() { - let (tx, rx) = mpsc::channel(10); - spawn(worker(rx)); - for i in 0..100 { - let resp = do_work(&tx, i).await; - println!("work result for iteration {i}: {resp}"); - } -} +{{#include pin.rs}} ```
diff --git a/src/concurrency/async-pitfalls/pin.rs b/src/concurrency/async-pitfalls/pin.rs new file mode 100644 index 000000000000..69efec6f9128 --- /dev/null +++ b/src/concurrency/async-pitfalls/pin.rs @@ -0,0 +1,48 @@ +use tokio::sync::{mpsc, oneshot}; +use tokio::task::spawn; +use tokio::time::{sleep, Duration}; + +// A work item. In this case, just sleep for the given time and respond +// with a message on the `respond_on` channel. +#[derive(Debug)] +struct Work { + input: u32, + respond_on: oneshot::Sender, +} + +// A worker which listens for work on a queue and performs it. +async fn worker(mut work_queue: mpsc::Receiver) { + let mut _iterations = 0; + loop { + tokio::select! { + Some(work) = work_queue.recv() => { + sleep(Duration::from_millis(10)).await; // Pretend to work. + work.respond_on + .send(work.input * 1000) + .expect("failed to send response"); + _iterations += 1; + } + // TODO: report number of iterations every 100ms + } + } +} + +// A requester which requests work and waits for it to complete. +async fn do_work(work_queue: &mpsc::Sender, input: u32) -> u32 { + let (tx, rx) = oneshot::channel(); + work_queue + .send(Work { input, respond_on: tx }) + .await + .expect("failed to send on work queue"); + rx.await.expect("failed waiting for response") +} + +#[tokio::main] +async fn main() { + let (tx, rx) = mpsc::channel(10); + spawn(worker(rx)); + for i in 0..100 { + let resp = do_work(&tx, i).await; + println!("work result for iteration {i}: {resp}"); + } +} diff --git a/src/concurrency/async/Cargo.toml b/src/concurrency/async/Cargo.toml new file mode 100644 index 000000000000..f60f74e2bd84 --- /dev/null +++ b/src/concurrency/async/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "async" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "tasks" +path = "tasks.rs" + +[dependencies] +anyhow = "1.0.81" +async-trait = "0.1.79" +futures = { version = "0.3.30", default-features = false } +reqwest = { version = "0.12.1", default-features = false } +tokio = { version = "1.36.0", features = ["full"] } diff --git a/src/concurrency/async/tasks.md b/src/concurrency/async/tasks.md index 2c23a0064c14..2941cd4a7f91 100644 --- a/src/concurrency/async/tasks.md +++ b/src/concurrency/async/tasks.md @@ -12,30 +12,7 @@ corresponding loosely to a call stack. Concurrency within a task is possible by polling multiple child futures, such as racing a timer and an I/O operation. ```rust,compile_fail -use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; -use tokio::net::TcpListener; - -#[tokio::main] -async fn main() -> io::Result<()> { - let listener = TcpListener::bind("127.0.0.1:0").await?; - println!("listening on port {}", listener.local_addr()?.port()); - - loop { - let (mut socket, addr) = listener.accept().await?; - - println!("connection from {addr:?}"); - - tokio::spawn(async move { - socket.write_all(b"Who are you?\n").await.expect("socket error"); - - let mut buf = vec![0; 1024]; - let name_size = socket.read(&mut buf).await.expect("socket error"); - let name = std::str::from_utf8(&buf[..name_size]).unwrap().trim(); - let reply = format!("Thanks for dialing in, {name}!\n"); - socket.write_all(reply.as_bytes()).await.expect("socket error"); - }); - } -} +{{#include tasks.rs}} ```
diff --git a/src/concurrency/async/tasks.rs b/src/concurrency/async/tasks.rs new file mode 100644 index 000000000000..14b1f8637521 --- /dev/null +++ b/src/concurrency/async/tasks.rs @@ -0,0 +1,24 @@ +use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpListener; + +#[tokio::main] +async fn main() -> io::Result<()> { + let listener = TcpListener::bind("127.0.0.1:0").await?; + println!("listening on port {}", listener.local_addr()?.port()); + + loop { + let (mut socket, addr) = listener.accept().await?; + + println!("connection from {addr:?}"); + + tokio::spawn(async move { + socket.write_all(b"Who are you?\n").await.expect("socket error"); + + let mut buf = vec![0; 1024]; + let name_size = socket.read(&mut buf).await.expect("socket error"); + let name = std::str::from_utf8(&buf[..name_size]).unwrap().trim(); + let reply = format!("Thanks for dialing in, {name}!\n"); + socket.write_all(reply.as_bytes()).await.expect("socket error"); + }); + } +} From 2cb8b9f33877d5158a9cb9f952d35487417929d0 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 24 Mar 2024 20:57:06 +0000 Subject: [PATCH 08/16] Test solution to async Dining Philosophers --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/concurrency/async-exercises/Cargo.toml | 12 ++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 src/concurrency/async-exercises/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 3154677fcd09..10f2cb18eae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,6 +158,13 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-exercises" +version = "0.1.0" +dependencies = [ + "tokio", +] + [[package]] name = "async-pitfalls" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b33c0d92e2b2..ad98fead19b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "src/control-flow-basics", "src/error-handling", "src/concurrency/sync-exercises", + "src/concurrency/async-exercises", "src/concurrency/async-exercises/chat-async", "src/generics", "src/iterators", diff --git a/src/concurrency/async-exercises/Cargo.toml b/src/concurrency/async-exercises/Cargo.toml new file mode 100644 index 000000000000..8bd284a32edb --- /dev/null +++ b/src/concurrency/async-exercises/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "async-exercises" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "dining-philosophers-async" +path = "dining-philosophers.rs" + +[dependencies] +tokio = { version = "1.36.0", features = ["full"] } From 87f504e601392a65d9cb315527343091ff3a18d9 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 24 Mar 2024 21:14:19 +0000 Subject: [PATCH 09/16] =?UTF-8?q?Consistently=20use=20lowercase=20for=20?= =?UTF-8?q?=E2=80=9Casync=20Rust=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/concurrency/async-exercises/dining-philosophers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/concurrency/async-exercises/dining-philosophers.md b/src/concurrency/async-exercises/dining-philosophers.md index a1d783cc5ca4..5c3bfff48695 100644 --- a/src/concurrency/async-exercises/dining-philosophers.md +++ b/src/concurrency/async-exercises/dining-philosophers.md @@ -36,7 +36,7 @@ code below to a file called `src/main.rs`, fill out the blanks, and test that } ``` -Since this time you are using Async Rust, you'll need a `tokio` dependency. You +Since this time you are using async Rust, you'll need a `tokio` dependency. You can use the following `Cargo.toml`: From c506af41e8933a5598dda669d0f5182e8520f5e0 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 7 Apr 2024 10:04:50 +0200 Subject: [PATCH 10/16] Mention class format on 1-day classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 1-day classes are sometimes taught to people who haven’t taken Rust Fundamentals, or who have taken it a while ago. So it seems nice to remind everybody that questions are very welcome. --- src/android.md | 5 +++++ src/bare-metal.md | 5 +++++ src/chromium.md | 5 +++++ src/concurrency/welcome.md | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/src/android.md b/src/android.md index 505eb9e80a45..6716ff6991ba 100644 --- a/src/android.md +++ b/src/android.md @@ -14,6 +14,11 @@ This course builds on [Rust Fundamentals](welcome-day-1.md) and we expect you are familiar with the basics of Rust. You should also be familiar with development on the Android Platform (AOSP). +## Class Format + +The class is meant to be very interactive! Please ask questions to drive the +exploration of Rust! + > We will attempt to call Rust from one of your own projects today. So try to > find a little corner of your code base where we can move some lines of code to > Rust. The fewer dependencies and "exotic" types the better. Something that diff --git a/src/bare-metal.md b/src/bare-metal.md index 7636d6e50a94..0a1ec5feaea2 100644 --- a/src/bare-metal.md +++ b/src/bare-metal.md @@ -20,3 +20,8 @@ The class is divided into several parts: This course builds on [Rust Fundamentals](welcome-day-1.md) and we expect you are familiar with the basics of Rust. You should ideally also have some experience with bare-metal programming in some other language such as C. + +## Class Format + +The class is meant to be very interactive! Please ask questions to drive the +exploration of Rust! diff --git a/src/chromium.md b/src/chromium.md index 4da37cc5bfe9..7a79cb18c8e6 100644 --- a/src/chromium.md +++ b/src/chromium.md @@ -18,3 +18,8 @@ code to connect between Rust and existing Chromium C++ code. This course builds on [Rust Fundamentals](welcome-day-1.md) and we expect you are familiar with the basics of Rust. You should also be familiar with Chromium development. + +## Class Format + +The class is meant to be very interactive! Please ask questions to drive the +exploration of Rust! diff --git a/src/concurrency/welcome.md b/src/concurrency/welcome.md index 60d5bb32814a..5edb39ae74b7 100644 --- a/src/concurrency/welcome.md +++ b/src/concurrency/welcome.md @@ -32,3 +32,8 @@ well as concepts such as: capture values from their environment. - [`Rc`](../smart-pointers/rc.md): we will use a similar type for shared ownership. + +## Class Format + +The class is meant to be very interactive! Please ask questions to drive the +exploration of Rust! From 9412254832a8ca1a033ae62b84be030d92608564 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Sun, 7 Apr 2024 10:05:02 +0200 Subject: [PATCH 11/16] =?UTF-8?q?Rephrase=20slide=20on=20=E2=80=9Cfearless?= =?UTF-8?q?=20concurrency=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bullet points makes it looks more like, well, a slide! --- src/concurrency/fearless.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/concurrency/fearless.md b/src/concurrency/fearless.md index 90919021a667..7ead3cc52f1b 100644 --- a/src/concurrency/fearless.md +++ b/src/concurrency/fearless.md @@ -1,9 +1,10 @@ # Fearless Concurrency -Rust has great support for concurrency and its powerful type system is able to -prevent many concurrency bugs at compile time. This is often referred to as -_fearless concurrency_ since you can rely on the compiler to ensure correctness -at runtime. +Rust has great support for concurrency: + +- The type system is able to prevent many concurrency bugs at compile time. +- This is often referred to as _fearless concurrency_. You can refactor without + fear of introducing concurrency issues.
From 512878a3e97e1cf06b3432e5ff711e88e8ce26cc Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Mon, 17 Jun 2024 12:31:44 +0200 Subject: [PATCH 12/16] wip: async dining philosophers simplification --- .../async-exercises/dining-philosophers.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/concurrency/async-exercises/dining-philosophers.rs b/src/concurrency/async-exercises/dining-philosophers.rs index 12cb08a96c9e..bf67e720031d 100644 --- a/src/concurrency/async-exercises/dining-philosophers.rs +++ b/src/concurrency/async-exercises/dining-philosophers.rs @@ -44,22 +44,11 @@ impl Philosopher { // Keep trying until we have both forks // ANCHOR_END: Philosopher-eat let (_left_fork, _right_fork) = loop { + tokio::task::yield_now().await; // Pick up forks... - let left_fork = self.left_fork.try_lock(); - let right_fork = self.right_fork.try_lock(); - let Ok(left_fork) = left_fork else { - // If we didn't get the left fork, drop the right fork if we - // have it and let other tasks make progress. - drop(right_fork); - time::sleep(time::Duration::from_millis(1)).await; - continue; - }; - let Ok(right_fork) = right_fork else { - // If we didn't get the right fork, drop the left fork and let - // other tasks make progress. - drop(left_fork); - time::sleep(time::Duration::from_millis(1)).await; - continue; + match (self.left_fork.try_lock(), self.right_fork.try_lock()) { + (Ok(left_fork), Ok(right_fork)) => (left_fork, right_fork), + (_, _) => continue, }; break (left_fork, right_fork); }; From 8d8a8b6f76195dd5ccc5858d69a76618ddf61baa Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Thu, 20 Jun 2024 00:29:42 +0200 Subject: [PATCH 13/16] Simplify `join_all` example This keeps the original structure, but removes 7 lines of code. --- src/concurrency/async-control-flow/join.rs | 23 ++++++++-------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/concurrency/async-control-flow/join.rs b/src/concurrency/async-control-flow/join.rs index 2fae1c87ebbe..1a5c2f2793c1 100644 --- a/src/concurrency/async-control-flow/join.rs +++ b/src/concurrency/async-control-flow/join.rs @@ -1,24 +1,17 @@ -use anyhow::Result; -use futures::future; +use futures::future::join_all; use reqwest; -use std::collections::HashMap; -async fn size_of_page(url: &str) -> Result { +async fn size_of_page(url: &str) -> reqwest::Result { let resp = reqwest::get(url).await?; Ok(resp.text().await?.len()) } #[tokio::main] async fn main() { - let urls: [&str; 4] = [ - "https://google.com", - "https://httpbin.org/ip", - "https://play.rust-lang.org/", - "BAD_URL", - ]; - let futures_iter = urls.into_iter().map(size_of_page); - let results = future::join_all(futures_iter).await; - let page_sizes_dict: HashMap<&str, Result> = - urls.into_iter().zip(results.into_iter()).collect(); - println!("{:?}", page_sizes_dict); + let urls = ["https://rust-lang.org", "https://httpbin.org/ip", "BAD_URL"]; + let futures = urls.into_iter().map(size_of_page); + let results = join_all(futures).await; + for (url, result) in urls.into_iter().zip(results) { + println!("{url}: {result:?}"); + } } From 317575131e22a10b74be6a7d16feba94cb24e9a4 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Thu, 20 Jun 2024 00:30:42 +0200 Subject: [PATCH 14/16] Simplify async channel example This removes a nested import (which I find hard to read) and removes an unnecessary type annotation. --- src/concurrency/async-control-flow/channels.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/concurrency/async-control-flow/channels.rs b/src/concurrency/async-control-flow/channels.rs index 3aefa055caad..19e49e6c7238 100644 --- a/src/concurrency/async-control-flow/channels.rs +++ b/src/concurrency/async-control-flow/channels.rs @@ -1,7 +1,7 @@ -use tokio::sync::mpsc::{self, Receiver}; +use tokio::sync::mpsc; -async fn ping_handler(mut input: Receiver<()>) { - let mut count: usize = 0; +async fn ping_handler(mut input: mpsc::Receiver<()>) { + let mut count = 0; while let Some(_) = input.recv().await { count += 1; From c4958b67defe9b8eff96216fac995f742b64a8b4 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Thu, 20 Jun 2024 00:46:36 +0200 Subject: [PATCH 15/16] Simplify pin example slightly This reduces the vertical space needed by the example, thus making it easier to keep on the screen while refactoring it. --- src/concurrency/async-pitfalls/pin.rs | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/concurrency/async-pitfalls/pin.rs b/src/concurrency/async-pitfalls/pin.rs index 69efec6f9128..6e03174a4ec3 100644 --- a/src/concurrency/async-pitfalls/pin.rs +++ b/src/concurrency/async-pitfalls/pin.rs @@ -1,25 +1,20 @@ +use anyhow::Result; use tokio::sync::{mpsc, oneshot}; -use tokio::task::spawn; use tokio::time::{sleep, Duration}; -// A work item. In this case, just sleep for the given time and respond -// with a message on the `respond_on` channel. #[derive(Debug)] struct Work { input: u32, respond_on: oneshot::Sender, } -// A worker which listens for work on a queue and performs it. async fn worker(mut work_queue: mpsc::Receiver) { let mut _iterations = 0; loop { tokio::select! { Some(work) = work_queue.recv() => { sleep(Duration::from_millis(10)).await; // Pretend to work. - work.respond_on - .send(work.input * 1000) - .expect("failed to send response"); + work.respond_on.send(work.input * 1000).unwrap(); _iterations += 1; } // TODO: report number of iterations every 100ms @@ -27,22 +22,19 @@ async fn worker(mut work_queue: mpsc::Receiver) { } } -// A requester which requests work and waits for it to complete. -async fn do_work(work_queue: &mpsc::Sender, input: u32) -> u32 { +async fn do_work(work_queue: &mpsc::Sender, input: u32) -> Result { let (tx, rx) = oneshot::channel(); - work_queue - .send(Work { input, respond_on: tx }) - .await - .expect("failed to send on work queue"); - rx.await.expect("failed waiting for response") + work_queue.send(Work { input, respond_on: tx }).await?; + Ok(rx.await?) } #[tokio::main] -async fn main() { +async fn main() -> Result<()> { let (tx, rx) = mpsc::channel(10); - spawn(worker(rx)); + tokio::spawn(worker(rx)); for i in 0..100 { - let resp = do_work(&tx, i).await; + let resp = do_work(&tx, i).await?; println!("work result for iteration {i}: {resp}"); } + Ok(()) } From 9d5daf868ed73ca02d8cc7665f6f28411194fe9a Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Mon, 22 Jul 2024 14:53:44 +0200 Subject: [PATCH 16/16] wip --- Cargo.lock | 4 ++++ Cargo.toml | 1 + src/SUMMARY.md | 2 +- src/concurrency/async-control-flow/channels.md | 2 +- src/concurrency/async-control-flow/channels.rs | 2 -- src/concurrency/async-exercises/chat-app.md | 13 ++++++------- .../async-exercises/dining-philosophers.rs | 4 +--- src/concurrency/async-pitfalls/async-traits.md | 6 +++--- src/concurrency/async-pitfalls/async-traits.rs | 12 ++++-------- src/concurrency/async-pitfalls/pin.md | 4 +++- 10 files changed, 24 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10f2cb18eae5..8b4ba1c5478a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,6 +373,10 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrency" +version = "0.1.0" + [[package]] name = "control-flow-basics" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ad98fead19b7..4cf316e641da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "src/borrowing", "src/control-flow-basics", "src/error-handling", + "src/concurrency", "src/concurrency/sync-exercises", "src/concurrency/async-exercises", "src/concurrency/async-exercises/chat-async", diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 5c0066add914..43ba612fb23a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -387,7 +387,7 @@ - [Futures](concurrency/async/futures.md) - [Runtimes](concurrency/async/runtimes.md) - [Tokio](concurrency/async/runtimes/tokio.md) - - [Tasks](concurrency/async/tasks.md) + - [Channels and Control Flow](concurrency/async-control-flow.md) - [Async Channels](concurrency/async-control-flow/channels.md) - [Join](concurrency/async-control-flow/join.md) diff --git a/src/concurrency/async-control-flow/channels.md b/src/concurrency/async-control-flow/channels.md index 497b69f5b833..6bab6257e885 100644 --- a/src/concurrency/async-control-flow/channels.md +++ b/src/concurrency/async-control-flow/channels.md @@ -4,7 +4,7 @@ minutes: 8 # Async Channels -Several crates have support for asynchronous channels. For instance `tokio`: +Asynchronous channels are very similar to synchronous channels: ```rust,editable,compile_fail {{#include channels.rs}} diff --git a/src/concurrency/async-control-flow/channels.rs b/src/concurrency/async-control-flow/channels.rs index 19e49e6c7238..8163db9d239b 100644 --- a/src/concurrency/async-control-flow/channels.rs +++ b/src/concurrency/async-control-flow/channels.rs @@ -2,12 +2,10 @@ use tokio::sync::mpsc; async fn ping_handler(mut input: mpsc::Receiver<()>) { let mut count = 0; - while let Some(_) = input.recv().await { count += 1; println!("Received {count} pings so far."); } - println!("ping_handler complete"); } diff --git a/src/concurrency/async-exercises/chat-app.md b/src/concurrency/async-exercises/chat-app.md index 1fc9bddbaf0e..416bc6e993a0 100644 --- a/src/concurrency/async-exercises/chat-app.md +++ b/src/concurrency/async-exercises/chat-app.md @@ -29,13 +29,13 @@ You are going to need the following functions from `tokio` and [`tokio_websockets`][2]. Spend a few minutes to familiarize yourself with the API. -- [StreamExt::next()][3] implemented by `WebSocketStream`: for asynchronously +- [`StreamExt::next()`][3] implemented by `WebSocketStream`: for asynchronously reading messages from a Websocket Stream. -- [SinkExt::send()][4] implemented by `WebSocketStream`: for asynchronously +- [`SinkExt::send()`][4] implemented by `WebSocketStream`: for asynchronously sending messages on a Websocket Stream. -- [Lines::next_line()][5]: for asynchronously reading user messages from the +- [`Lines::next_line()`][5]: for asynchronously reading user messages from the standard input. -- [Sender::subscribe()][6]: for subscribing to a broadcast channel. +- [`Sender::subscribe()`][6]: for subscribing to a broadcast channel. ## Two binaries @@ -58,9 +58,8 @@ _src/bin/server.rs_: {{#include chat-async/src/bin/server.rs:setup}} {{#include chat-async/src/bin/server.rs:handle_connection}} - // TODO: For a hint, see the description of the task below. - + Ok(()) {{#include chat-async/src/bin/server.rs:main}} ``` @@ -72,7 +71,7 @@ _src/bin/client.rs_: {{#include chat-async/src/bin/client.rs:setup}} // TODO: For a hint, see the description of the task below. - + Ok(()) } ``` diff --git a/src/concurrency/async-exercises/dining-philosophers.rs b/src/concurrency/async-exercises/dining-philosophers.rs index bf67e720031d..537bbec73f11 100644 --- a/src/concurrency/async-exercises/dining-philosophers.rs +++ b/src/concurrency/async-exercises/dining-philosophers.rs @@ -44,13 +44,11 @@ impl Philosopher { // Keep trying until we have both forks // ANCHOR_END: Philosopher-eat let (_left_fork, _right_fork) = loop { - tokio::task::yield_now().await; // Pick up forks... match (self.left_fork.try_lock(), self.right_fork.try_lock()) { - (Ok(left_fork), Ok(right_fork)) => (left_fork, right_fork), + (Ok(left_fork), Ok(right_fork)) => break (left_fork, right_fork), (_, _) => continue, }; - break (left_fork, right_fork); }; // ANCHOR: Philosopher-eat-body diff --git a/src/concurrency/async-pitfalls/async-traits.md b/src/concurrency/async-pitfalls/async-traits.md index 00140affcafa..0d7cf7ddad37 100644 --- a/src/concurrency/async-pitfalls/async-traits.md +++ b/src/concurrency/async-pitfalls/async-traits.md @@ -4,9 +4,9 @@ minutes: 5 # Async Traits -Async methods in traits are were stabilized in the 1.75 release. This required -support for using return-position `impl Trait` in traits, as the desugaring for -`async fn` includes `-> impl Future`. +Async methods in traits are were stabilized in the 1.75 release (December 2023). +This required support for using return-position `impl Trait` in traits, as the +desugaring for `async fn` includes `-> impl Future`. However, even with the native support, there are some pitfalls around `async fn`: diff --git a/src/concurrency/async-pitfalls/async-traits.rs b/src/concurrency/async-pitfalls/async-traits.rs index af50c89b9a24..be3abcce69b2 100644 --- a/src/concurrency/async-pitfalls/async-traits.rs +++ b/src/concurrency/async-pitfalls/async-traits.rs @@ -1,5 +1,4 @@ use async_trait::async_trait; -use std::time::Instant; use tokio::time::{sleep, Duration}; #[async_trait] @@ -18,14 +17,11 @@ impl Sleeper for FixedSleeper { } } -async fn run_all_sleepers_multiple_times( - sleepers: Vec>, - n_times: usize, -) { - for _ in 0..n_times { +async fn run_all_sleepers_multiple_times(sleepers: Vec>) { + for _ in 0..5 { println!("Running all sleepers..."); for sleeper in &sleepers { - let start = Instant::now(); + let start = std::time::Instant::now(); sleeper.sleep().await; println!("Slept for {} ms", start.elapsed().as_millis()); } @@ -38,5 +34,5 @@ async fn main() { Box::new(FixedSleeper { sleep_ms: 50 }), Box::new(FixedSleeper { sleep_ms: 100 }), ]; - run_all_sleepers_multiple_times(sleepers, 5).await; + run_all_sleepers_multiple_times(sleepers).await; } diff --git a/src/concurrency/async-pitfalls/pin.md b/src/concurrency/async-pitfalls/pin.md index 85be24acae45..84bf8795e44a 100644 --- a/src/concurrency/async-pitfalls/pin.md +++ b/src/concurrency/async-pitfalls/pin.md @@ -5,9 +5,11 @@ minutes: 20 # `Pin` Async blocks and functions return types implementing the `Future` trait. The -type returned is the result of a compiler transformation which turns local +type returned is the result of a [compiler transformation] which turns local variables into data stored inside the future. +[compiler transformation]: https://tokio.rs/tokio/tutorial/async + Some of those variables can hold pointers to other local variables. Because of that, the future should never be moved to a different memory location, as it would invalidate those pointers.