From 30c83000749dcfc6cd7c399e47bf114428db9943 Mon Sep 17 00:00:00 2001 From: chrischiedo <8336086+chrischiedo@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:51:16 +0300 Subject: [PATCH 1/2] add async-await exercises --- dev/Cargo.toml | 3 + exercises/24_async_await/README.md | 15 ++++ exercises/24_async_await/async_await1.rs | 27 +++++++ exercises/24_async_await/async_await2.rs | 32 ++++++++ exercises/24_async_await/async_await3.rs | 42 ++++++++++ exercises/24_async_await/async_await4.rs | 93 +++++++++++++++++++++++ exercises/README.md | 1 + rustlings-macros/info.toml | 54 +++++++++++++ solutions/24_async_await/async_await1.rs | 29 +++++++ solutions/24_async_await/async_await2.rs | 32 ++++++++ solutions/24_async_await/async_await3.rs | 42 ++++++++++ solutions/24_async_await/async_await4.rs | 97 ++++++++++++++++++++++++ 12 files changed, 467 insertions(+) create mode 100644 exercises/24_async_await/README.md create mode 100644 exercises/24_async_await/async_await1.rs create mode 100644 exercises/24_async_await/async_await2.rs create mode 100644 exercises/24_async_await/async_await3.rs create mode 100644 exercises/24_async_await/async_await4.rs create mode 100644 solutions/24_async_await/async_await1.rs create mode 100644 solutions/24_async_await/async_await2.rs create mode 100644 solutions/24_async_await/async_await3.rs create mode 100644 solutions/24_async_await/async_await4.rs diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 66bc1dfea0..020db249f6 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -196,6 +196,9 @@ edition = "2024" # Don't publish the exercises on crates.io! publish = false +[dependencies] +trpl = "0.3.0" + [profile.release] panic = "abort" diff --git a/exercises/24_async_await/README.md b/exercises/24_async_await/README.md new file mode 100644 index 0000000000..ca05c4bbd6 --- /dev/null +++ b/exercises/24_async_await/README.md @@ -0,0 +1,15 @@ +# Async/Await + +Rust's async model lets you write concurrent code that waits for I/O or other +work without blocking a thread. An `async fn` returns a `Future` — a value that +represents work still in progress. The `.await` keyword pauses until that work +finishes. + +Futures don't run on their own: an **async runtime** (such as [Tokio](https://tokio.rs)) +polls them to completion. Rustlings uses Tokio (via the [`trpl`](https://crates.io/crates/trpl) crate) for these exercises. + +## Further information + +- [The Rust Programming Language: Async Programming](https://doc.rust-lang.org/book/ch17-00-async-await.html) +- [Async Programming Book](https://rust-lang.github.io/async-book/) +- [Tokio tutorial](https://tokio.rs/tokio/tutorial) diff --git a/exercises/24_async_await/async_await1.rs b/exercises/24_async_await/async_await1.rs new file mode 100644 index 0000000000..14c5e7235b --- /dev/null +++ b/exercises/24_async_await/async_await1.rs @@ -0,0 +1,27 @@ +// This program fetches a greeting asynchronously. Async functions return a +// `Future` that must be `.await`ed to get the result. An async runtime like +// Tokio is needed to drive futures to completion. +use std::time::Duration; + +async fn fetch_greeting() -> String { + trpl::sleep(Duration::from_millis(10)).await; + String::from("Hello, async world!") +} + +fn main() { + trpl::block_on(async { + // TODO: We need to `await` the future returned by `fetch_greeting`. + let greeting = fetch_greeting(); + println!("{greeting}"); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fetch_greeting_returns_expected_message() { + trpl::block_on(async { assert_eq!(fetch_greeting().await, "Hello, async world!") }); + } +} diff --git a/exercises/24_async_await/async_await2.rs b/exercises/24_async_await/async_await2.rs new file mode 100644 index 0000000000..a8b137eb95 --- /dev/null +++ b/exercises/24_async_await/async_await2.rs @@ -0,0 +1,32 @@ +// This program runs multiple async operations. The `Future` trait represents an +// asynchronous computation. Multiple futures can be polled concurrently with +// macros like `trpl::join!`. +use std::time::Duration; + +async fn compute_value(id: u32) -> u32 { + trpl::sleep(Duration::from_millis(10)).await; + id * 10 +} + +async fn compute_all() -> Vec { + // TODO: Use `trpl::join!` to await three futures concurrently. + // Collect the results into a vector, e.g. `[10, 20, 30]`. + todo!() +} + +fn main() { + trpl::block_on(async { + let results = compute_all().await; + println!("Computed: {results:?}"); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compute_all_returns_expected_values() { + trpl::block_on(async { assert_eq!(compute_all().await, [10, 20, 30]) }); + } +} diff --git a/exercises/24_async_await/async_await3.rs b/exercises/24_async_await/async_await3.rs new file mode 100644 index 0000000000..d093ce63e6 --- /dev/null +++ b/exercises/24_async_await/async_await3.rs @@ -0,0 +1,42 @@ +// Async runtimes like Tokio can spawn independent tasks that run concurrently +// on the runtime's thread pool. Each spawned task returns a `JoinHandle` that +// can be `.await`ed to get its result — similar to `thread::spawn` and `join`. +use std::time::Duration; + +async fn double(n: u32) -> u32 { + trpl::sleep(Duration::from_millis(10)).await; + n * 2 +} + +async fn double_all(values: &[u32]) -> Vec { + let mut handles = Vec::new(); + for &value in values { + // TODO: Spawn `double(value)` on the Tokio runtime using `trpl::spawn_task`. + // Push the returned `JoinHandle` into `handles`. + todo!(); + } + + let mut results = Vec::new(); + for handle in handles { + // TODO: Await each spawned task and collect its result into `results`. + } + + results +} + +fn main() { + trpl::block_on(async { + let results = double_all(&[1, 2, 3, 4, 5]).await; + println!("Results: {results:?}"); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compute_all_double_values() { + trpl::block_on(async { assert_eq!(double_all(&[1, 2, 3, 4, 5]).await, [2, 4, 6, 8, 10]) }); + } +} diff --git a/exercises/24_async_await/async_await4.rs b/exercises/24_async_await/async_await4.rs new file mode 100644 index 0000000000..b0994da68f --- /dev/null +++ b/exercises/24_async_await/async_await4.rs @@ -0,0 +1,93 @@ +// This exercise demonstrates async message-passing concurrency using a channel. +use std::time::Duration; + +struct Queue { + first_half: Vec, + second_half: Vec, +} + +impl Queue { + fn new() -> Self { + Self { + first_half: vec![ + String::from("winter"), + String::from("is"), + String::from("really"), + String::from("coming"), + ], + second_half: vec![ + String::from("we"), + String::from("do"), + String::from("not"), + String::from("sow"), + ], + } + } +} + +async fn transmit(q: Queue, tx: trpl::Sender) { + // TODO: We want to send `tx` to both tasks. But currently, it is moved + // into the first task (future). What change do we need to make in order to + // solve for this issue? + + let tx_fut1 = async move { + for val in q.first_half { + tx.send(val).unwrap(); + trpl::sleep(Duration::from_millis(250)).await; + } + }; + + let tx_fut2 = async move { + for val in q.second_half { + tx.send(val).unwrap(); + trpl::sleep(Duration::from_millis(500)).await; + } + }; + + trpl::join(tx_fut1, tx_fut2).await; +} + +fn main() { + trpl::block_on(async { + let (tx, mut rx) = trpl::channel(); + + let queue = Queue::new(); + + let tx_fut = transmit(queue, tx); + + // TODO: Define an `rx_fut` async block that loops through all received values from `rx` + // and processes each one. + + // TODO: Wait on the two futures (`tx_fut` and `rx_fut`) to complete. + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn all_messages_are_received() { + trpl::block_on(async { + let (tx, mut rx) = trpl::channel(); + let queue = Queue::new(); + + transmit(queue, tx).await; + + let mut received = Vec::new(); + while let Some(val) = rx.recv().await { + received.push(val); + } + + received.sort(); + + let expected: Vec = + vec!["coming", "do", "is", "not", "really", "sow", "we", "winter"] + .into_iter() + .map(String::from) + .collect(); + + assert_eq!(received, expected); + }); + } +} diff --git a/exercises/README.md b/exercises/README.md index 24ebd06965..8b941a7af4 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -25,3 +25,4 @@ | macros | §20.5 | | clippy | Appendix D | | conversions | n/a | +| async_await | §17 | diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index a4c72c124a..39ffa33006 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -1211,3 +1211,57 @@ name = "conversions5" dir = "23_conversions" hint = """ Add `AsRef` or `AsMut` as a trait bound to the functions.""" + +# ASYNC/AWAIT + +[[exercises]] +name = "async_await1" +dir = "24_async_await" +hint = """ +An `async fn` returns a `Future`, not the final value. Use `.await` to run the +future to completion and get the result. + +Rust doesn't ship with an async runtime in the standard library. `Tokio` is a popular +async runtime for Rust that provides an event loop and task scheduling. +The `trpl::block_on()` function runs a single future to completion on the Tokio `Runtime`. +Every time it's called, a new instance of `tokio::runtime::Runtime` is created. + +See the Async Programming chapter of the Rust book: +https://doc.rust-lang.org/book/ch17-01-futures-and-syntax.html""" + +[[exercises]] +name = "async_await2" +dir = "24_async_await" +hint = """ +`trpl::join!` takes multiple futures and polls them concurrently. It returns a +tuple with all results once every future has completed. + +Example: +```rust +let (a, b) = trpl::join!(future_a(), future_b()); +``` + +Resource: https://doc.rust-lang.org/book/ch17-02-concurrency-with-async.html""" + +[[exercises]] +name = "async_await3" +dir = "24_async_await" +hint = """ +Use `trpl::spawn_task` to run a future as an independent task on the runtime. +It returns a `JoinHandle` which you can `.await` to get the task's output. + +This is the async equivalent of spawning threads and calling `join` on them. + +Resource: https://doc.rust-lang.org/book/ch17-02-concurrency-with-async.html""" + +[[exercises]] +name = "async_await4" +dir = "24_async_await" +hint = """ +The async channel is a multiple-producer channel, so we can send multiple messages +from multiple futures. + +Then we can use `trpl::join!` to await multiple futures to complete. `trpl::join!` +cooperatively polls futures on the same executor thread. + +Resource: https://doc.rust-lang.org/book/ch17-02-concurrency-with-async.html""" diff --git a/solutions/24_async_await/async_await1.rs b/solutions/24_async_await/async_await1.rs new file mode 100644 index 0000000000..83a0213d1d --- /dev/null +++ b/solutions/24_async_await/async_await1.rs @@ -0,0 +1,29 @@ +// This program fetches a greeting asynchronously. Async functions return a +// `Future` that must be `.await`ed to get the result. An async runtime like +// Tokio is needed to drive futures to completion. +use std::time::Duration; + +async fn fetch_greeting() -> String { + trpl::sleep(Duration::from_millis(10)).await; + String::from("Hello, async world!") +} + +fn main() { + // `trpl::block_on()` runs a single future to completion on the Tokio `Runtime`. + // Every time it's called, a new instance of `tokio::runtime::Runtime` will be created. + trpl::block_on(async { + // `.await` suspends until the future completes and yields the `String`. + let greeting = fetch_greeting().await; + println!("{greeting}"); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fetch_greeting_returns_expected_message() { + trpl::block_on(async { assert_eq!(fetch_greeting().await, "Hello, async world!") }); + } +} diff --git a/solutions/24_async_await/async_await2.rs b/solutions/24_async_await/async_await2.rs new file mode 100644 index 0000000000..72c50f1a3d --- /dev/null +++ b/solutions/24_async_await/async_await2.rs @@ -0,0 +1,32 @@ +// This program runs multiple async operations. The `Future` trait represents an +// asynchronous computation. Multiple futures can be polled concurrently with +// macros like `trpl::join!`. +use std::time::Duration; + +async fn compute_value(id: u32) -> u32 { + trpl::sleep(Duration::from_millis(10)).await; + id * 10 +} + +async fn compute_all() -> Vec { + // `trpl::join!` polls all futures concurrently and returns a tuple of results. + let (a, b, c) = trpl::join!(compute_value(1), compute_value(2), compute_value(3)); + vec![a, b, c] +} + +fn main() { + trpl::block_on(async { + let results = compute_all().await; + println!("Computed: {results:?}"); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compute_all_returns_expected_values() { + trpl::block_on(async { assert_eq!(compute_all().await, [10, 20, 30]) }); + } +} diff --git a/solutions/24_async_await/async_await3.rs b/solutions/24_async_await/async_await3.rs new file mode 100644 index 0000000000..4b5e451467 --- /dev/null +++ b/solutions/24_async_await/async_await3.rs @@ -0,0 +1,42 @@ +// Async runtimes like Tokio can spawn independent tasks that run concurrently +// on the runtime's thread pool. Each spawned task returns a `JoinHandle` that +// can be `.await`ed to get its result — similar to `thread::spawn` and `join`. +use std::time::Duration; + +async fn double(n: u32) -> u32 { + trpl::sleep(Duration::from_millis(10)).await; + n * 2 +} + +async fn double_all(values: &[u32]) -> Vec { + let mut handles = Vec::new(); + for &value in values { + // `trpl::spawn_task` schedules the future on the runtime's executor. + handles.push(trpl::spawn_task(double(value))); + } + + let mut results = Vec::new(); + for handle in handles { + // Awaiting the `JoinHandle` waits for the spawned task to finish. + results.push(handle.await.unwrap()); + } + + results +} + +fn main() { + trpl::block_on(async { + let results = double_all(&[1, 2, 3, 4, 5]).await; + println!("Results: {results:?}"); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compute_all_double_values() { + trpl::block_on(async { assert_eq!(double_all(&[1, 2, 3, 4, 5]).await, [2, 4, 6, 8, 10]) }); + } +} diff --git a/solutions/24_async_await/async_await4.rs b/solutions/24_async_await/async_await4.rs new file mode 100644 index 0000000000..854ae40796 --- /dev/null +++ b/solutions/24_async_await/async_await4.rs @@ -0,0 +1,97 @@ +// This exercise demonstrates async message-passing concurrency using a channel. +use std::time::Duration; + +struct Queue { + first_half: Vec, + second_half: Vec, +} + +impl Queue { + fn new() -> Self { + Self { + first_half: vec![ + String::from("winter"), + String::from("is"), + String::from("really"), + String::from("coming"), + ], + second_half: vec![ + String::from("we"), + String::from("do"), + String::from("not"), + String::from("sow"), + ], + } + } +} + +async fn transmit(q: Queue, tx: trpl::Sender) { + // Clone the sender `tx` first. + let tx_clone = tx.clone(); + + let tx_fut1 = async move { + for val in q.first_half { + // Then we use the clone here + tx_clone.send(val).unwrap(); + trpl::sleep(Duration::from_millis(250)).await; + } + }; + + let tx_fut2 = async move { + for val in q.second_half { + // And here we use the original sender `tx` + tx.send(val).unwrap(); + trpl::sleep(Duration::from_millis(500)).await; + } + }; + + trpl::join(tx_fut1, tx_fut2).await; +} + +fn main() { + trpl::block_on(async { + let (tx, mut rx) = trpl::channel(); + + let queue = Queue::new(); + + let tx_fut = transmit(queue, tx); + + let rx_fut = async { + while let Some(value) = rx.recv().await { + println!("Received: {value:?}"); + } + }; + + trpl::join!(tx_fut, rx_fut); // OR `trpl::join(tx_fut, rx_fut).await` + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn all_messages_are_received() { + trpl::block_on(async { + let (tx, mut rx) = trpl::channel(); + let queue = Queue::new(); + + transmit(queue, tx).await; + + let mut received = Vec::new(); + while let Some(val) = rx.recv().await { + received.push(val); + } + + received.sort(); + + let expected: Vec = + vec!["coming", "do", "is", "not", "really", "sow", "we", "winter"] + .into_iter() + .map(String::from) + .collect(); + + assert_eq!(received, expected); + }); + } +} From bc83fd9177fe5760dbdc92a61f615a10c491db2e Mon Sep 17 00:00:00 2001 From: chrischiedo <8336086+chrischiedo@users.noreply.github.com> Date: Thu, 18 Jun 2026 09:48:51 +0300 Subject: [PATCH 2/2] run 'cargo dev update' to resolve build failure --- dev/Cargo.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 020db249f6..035a2e4c84 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -188,6 +188,14 @@ bin = [ { name = "conversions4_sol", path = "../solutions/23_conversions/conversions4.rs" }, { name = "conversions5", path = "../exercises/23_conversions/conversions5.rs" }, { name = "conversions5_sol", path = "../solutions/23_conversions/conversions5.rs" }, + { name = "async_await1", path = "../exercises/24_async_await/async_await1.rs" }, + { name = "async_await1_sol", path = "../solutions/24_async_await/async_await1.rs" }, + { name = "async_await2", path = "../exercises/24_async_await/async_await2.rs" }, + { name = "async_await2_sol", path = "../solutions/24_async_await/async_await2.rs" }, + { name = "async_await3", path = "../exercises/24_async_await/async_await3.rs" }, + { name = "async_await3_sol", path = "../solutions/24_async_await/async_await3.rs" }, + { name = "async_await4", path = "../exercises/24_async_await/async_await4.rs" }, + { name = "async_await4_sol", path = "../solutions/24_async_await/async_await4.rs" }, ] [package]