From abc8969dd02a06f479774af894381997199d1963 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 17 Apr 2026 15:14:01 +0200 Subject: [PATCH 1/4] Add exercise async1 The goal here was to get the first bit of "muscle memory" for using the async and await keywords. The little story should make it more intuitive for users why asynchronous programming is needed in the first place. This exercise will be moved to the location corresponding to the book in a later commit, to keep the diff of this one clean. --- dev/Cargo.toml | 5 ++++ exercises/24_async/README.md | 13 +++++++++ exercises/24_async/async1.rs | 55 ++++++++++++++++++++++++++++++++++++ rustlings-macros/info.toml | 14 +++++++++ solutions/24_async/async1.rs | 53 ++++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+) create mode 100644 exercises/24_async/README.md create mode 100644 exercises/24_async/async1.rs create mode 100644 solutions/24_async/async1.rs diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 4f725b704b..3583e59d53 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -188,6 +188,8 @@ bin = [ { name = "try_from_into_sol", path = "../solutions/23_conversions/try_from_into.rs" }, { name = "as_ref_mut", path = "../exercises/23_conversions/as_ref_mut.rs" }, { name = "as_ref_mut_sol", path = "../solutions/23_conversions/as_ref_mut.rs" }, + { name = "async1", path = "../exercises/24_async/async1.rs" }, + { name = "async1_sol", path = "../solutions/24_async/async1.rs" }, ] [package] @@ -196,6 +198,9 @@ edition = "2024" # Don't publish the exercises on crates.io! publish = false +[dependencies] +tokio = { version = "1.52.1", features = ["rt"] } + [profile.release] panic = "abort" diff --git a/exercises/24_async/README.md b/exercises/24_async/README.md new file mode 100644 index 0000000000..7928650ef1 --- /dev/null +++ b/exercises/24_async/README.md @@ -0,0 +1,13 @@ +# Async + +Asynchronous programming is a model where tasks are delegated to a runtime that executes them concurrently. +It is particularly efficient for applications where many independent IO-operations are performed, e.g. web servers. + +Rust provides the necessary primitives to do asynchronous programming in the language. +However, Rust's standard library does not include a runtime. +For these exercises, we will use the popular runtime called `tokio`. + +## Further information + +- [Fundamentals of Asynchronous Programming](https://doc.rust-lang.org/book/ch17-00-async-await.html) +- [Tokio documentation](https://docs.rs/tokio/latest/tokio/) diff --git a/exercises/24_async/async1.rs b/exercises/24_async/async1.rs new file mode 100644 index 0000000000..5810ced31a --- /dev/null +++ b/exercises/24_async/async1.rs @@ -0,0 +1,55 @@ +// Tim has to complete a few chores today, before he's allowed to play soccer +// with his friends. His friends decide to help him. Working together, they +// finish the chores earlier and have more time left to play soccer. +// +// Let's simulate this using asynchronous programming. Each boy is represented +// as an asynchronous task, which can be executed concurrently (they can be +// working at the same time). + +use std::sync::atomic::{AtomicU8, Ordering}; + +// Used by "mom" to check that all chores are done before Tim plays soccer :-) +static CHORES_DONE: AtomicU8 = AtomicU8::new(0); + +fn main() { + // Async tasks need to be executed by a "runtime", which is not provided by + // Rust's standard library. We use the popular "tokio" runtime here. + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); + + // TODO: Fix the compiler errors by making the spawned function async. + let task_tim = rt.spawn(tim()); + let task_carl = rt.spawn(carl()); + let task_nick = rt.spawn(nick()); + + // Block the runtime on a task that waits for all boys to finish the chores. + // TODO: "await" all three tasks to fix the compiler errors. + rt.block_on(async { + task_tim; + task_carl; + task_nick; + }); + + assert_eq!( + CHORES_DONE.load(Ordering::SeqCst), + 3, + "Did you (a)wait for all the boys to finish the chores?" + ); + println!("Ready to play soccer!"); +} + +fn tim() { + println!("Cleaning my room..."); + CHORES_DONE.fetch_add(1, Ordering::SeqCst); +} + +fn carl() { + println!("Washing the dishes..."); + CHORES_DONE.fetch_add(1, Ordering::SeqCst); +} + +fn nick() { + println!("Mowing the lawn..."); + CHORES_DONE.fetch_add(1, Ordering::SeqCst); +} diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 353d405c11..8266ea995d 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -1199,3 +1199,17 @@ name = "as_ref_mut" dir = "23_conversions" hint = """ Add `AsRef` or `AsMut` as a trait bound to the functions.""" + +# ASYNC + +[[exercises]] +name = "async1" +dir = "24_async" +test = false +hint = """ +Asynchronous runtimes like tokio can only spawn tasks that are defined as async +functions, not regular ones. Add the "async" keyword before the "fn" keyword of +the functions "tim", "carl" and "nick". + +An async task can wait for another one to complete by "awaiting" it. Add +".await" after the three "task_name" variables in the "block_on" call.""" diff --git a/solutions/24_async/async1.rs b/solutions/24_async/async1.rs new file mode 100644 index 0000000000..a897067a64 --- /dev/null +++ b/solutions/24_async/async1.rs @@ -0,0 +1,53 @@ +// Tim has to complete a few chores today, before he's allowed to play soccer +// with his friends. His friends decide to help him. Working together, they +// finish the chores earlier and have more time left to play soccer. +// +// Let's simulate this using asynchronous programming. Each boy is represented +// as an asynchronous task, which can be executed concurrently (they can be +// working at the same time). + +use std::sync::atomic::{AtomicU8, Ordering}; + +// Used by "mom" to check that all chores are done before Tim plays soccer :-) +static CHORES_DONE: AtomicU8 = AtomicU8::new(0); + +fn main() { + // Async tasks need to be executed by a "runtime", which is not provided by + // Rust's standard library. We use the popular "tokio" runtime here. + let rt = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); + + let task_tim = rt.spawn(tim()); + let task_carl = rt.spawn(carl()); + let task_nick = rt.spawn(nick()); + + // Block the runtime on a task that waits for all boys to finish the chores. + rt.block_on(async { + task_tim.await.unwrap(); + task_carl.await.unwrap(); + task_nick.await.unwrap(); + }); + + assert_eq!( + CHORES_DONE.load(Ordering::SeqCst), + 3, + "Did you (a)wait for all the boys to finish the chores?" + ); + println!("Ready to play soccer!"); +} + +async fn tim() { + println!("Cleaning my room..."); + CHORES_DONE.fetch_add(1, Ordering::SeqCst); +} + +async fn carl() { + println!("Washing the dishes..."); + CHORES_DONE.fetch_add(1, Ordering::SeqCst); +} + +async fn nick() { + println!("Mowing the lawn..."); + CHORES_DONE.fetch_add(1, Ordering::SeqCst); +} From db67a3d8c229319e87a00c007feed2c6e3860148 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 25 Apr 2026 15:05:52 +0200 Subject: [PATCH 2/4] Relax version requirement for tokio --- dev/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 3583e59d53..59c69c12b2 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -199,7 +199,7 @@ edition = "2024" publish = false [dependencies] -tokio = { version = "1.52.1", features = ["rt"] } +tokio = { version = "1", features = ["rt"] } [profile.release] panic = "abort" From 5f9718cd2e359d823ee57015c8bce0608510b96d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 25 Apr 2026 15:06:27 +0200 Subject: [PATCH 3/4] Refer to tokio as "mainstream" instead of "popular" --- exercises/24_async/README.md | 2 +- exercises/24_async/async1.rs | 2 +- solutions/24_async/async1.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/24_async/README.md b/exercises/24_async/README.md index 7928650ef1..4e61bc32b2 100644 --- a/exercises/24_async/README.md +++ b/exercises/24_async/README.md @@ -5,7 +5,7 @@ It is particularly efficient for applications where many independent IO-operatio Rust provides the necessary primitives to do asynchronous programming in the language. However, Rust's standard library does not include a runtime. -For these exercises, we will use the popular runtime called `tokio`. +For these exercises, we will use the mainstream runtime called `tokio`. ## Further information diff --git a/exercises/24_async/async1.rs b/exercises/24_async/async1.rs index 5810ced31a..40d71bc0f8 100644 --- a/exercises/24_async/async1.rs +++ b/exercises/24_async/async1.rs @@ -13,7 +13,7 @@ static CHORES_DONE: AtomicU8 = AtomicU8::new(0); fn main() { // Async tasks need to be executed by a "runtime", which is not provided by - // Rust's standard library. We use the popular "tokio" runtime here. + // Rust's standard library. Here, we use the mainstream runtime `tokio`. let rt = tokio::runtime::Builder::new_current_thread() .build() .unwrap(); diff --git a/solutions/24_async/async1.rs b/solutions/24_async/async1.rs index a897067a64..ae9e0c37f7 100644 --- a/solutions/24_async/async1.rs +++ b/solutions/24_async/async1.rs @@ -13,7 +13,7 @@ static CHORES_DONE: AtomicU8 = AtomicU8::new(0); fn main() { // Async tasks need to be executed by a "runtime", which is not provided by - // Rust's standard library. We use the popular "tokio" runtime here. + // Rust's standard library. Here, we use the mainstream runtime `tokio`. let rt = tokio::runtime::Builder::new_current_thread() .build() .unwrap(); From 91fd773859d319f9c94afc160ee5e867a70755e8 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 25 Apr 2026 15:07:13 +0200 Subject: [PATCH 4/4] Change story of exercise async1 - Remove confusing use of atomics. Use return values of async tasks instead, to ensure all tasks are awaited. - Remove use of `println!()`, which uses a global lock and cannot be executed in parallel. --- exercises/24_async/async1.rs | 67 +++++++++++++++--------------------- solutions/24_async/async1.rs | 67 +++++++++++++++--------------------- 2 files changed, 55 insertions(+), 79 deletions(-) diff --git a/exercises/24_async/async1.rs b/exercises/24_async/async1.rs index 40d71bc0f8..8c2067ed4a 100644 --- a/exercises/24_async/async1.rs +++ b/exercises/24_async/async1.rs @@ -1,15 +1,11 @@ -// Tim has to complete a few chores today, before he's allowed to play soccer -// with his friends. His friends decide to help him. Working together, they -// finish the chores earlier and have more time left to play soccer. +// Alice is an elementary school teacher who needs to calculate the mean test +// score for three classes she teaches. Instead of calculating them one after +// the other, she decides to ask her friends Bob and Catherine for help. Working +// together, they can finish the job much faster. // -// Let's simulate this using asynchronous programming. Each boy is represented -// as an asynchronous task, which can be executed concurrently (they can be -// working at the same time). - -use std::sync::atomic::{AtomicU8, Ordering}; - -// Used by "mom" to check that all chores are done before Tim plays soccer :-) -static CHORES_DONE: AtomicU8 = AtomicU8::new(0); +// Let's simulate this using asynchronous programming. Each person is +// represented as an asynchronous task, which can be executed concurrently (i.e. +// they can be doing the calculations at the same time). fn main() { // Async tasks need to be executed by a "runtime", which is not provided by @@ -18,38 +14,29 @@ fn main() { .build() .unwrap(); - // TODO: Fix the compiler errors by making the spawned function async. - let task_tim = rt.spawn(tim()); - let task_carl = rt.spawn(carl()); - let task_nick = rt.spawn(nick()); + let scores_class_a = &[83, 77, 92]; + let scores_class_b = &[84, 88, 96]; + let scores_class_c = &[71, 83, 76]; - // Block the runtime on a task that waits for all boys to finish the chores. - // TODO: "await" all three tasks to fix the compiler errors. - rt.block_on(async { - task_tim; - task_carl; - task_nick; + // TODO: Fix the compiler errors by making the spawned function async. + let alice = rt.spawn(calculate_mean_score(scores_class_a)); + let bob = rt.spawn(calculate_mean_score(scores_class_b)); + let catherine = rt.spawn(calculate_mean_score(scores_class_c)); + + // Block the runtime on a task that awaits all three calculations. + let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async { + [ + // TODO: "await" all three tasks to fix the compiler error. + alice, bob, catherine, + ] }); - assert_eq!( - CHORES_DONE.load(Ordering::SeqCst), - 3, - "Did you (a)wait for all the boys to finish the chores?" - ); - println!("Ready to play soccer!"); -} - -fn tim() { - println!("Cleaning my room..."); - CHORES_DONE.fetch_add(1, Ordering::SeqCst); -} - -fn carl() { - println!("Washing the dishes..."); - CHORES_DONE.fetch_add(1, Ordering::SeqCst); + assert_eq!(mean_score_a, 84); + assert_eq!(mean_score_b, 89); + assert_eq!(mean_score_c, 76); } -fn nick() { - println!("Mowing the lawn..."); - CHORES_DONE.fetch_add(1, Ordering::SeqCst); +fn calculate_mean_score(score_list: &[usize]) -> usize { + let score_sum: usize = score_list.iter().sum(); + score_sum / score_list.len() } diff --git a/solutions/24_async/async1.rs b/solutions/24_async/async1.rs index ae9e0c37f7..925be76675 100644 --- a/solutions/24_async/async1.rs +++ b/solutions/24_async/async1.rs @@ -1,15 +1,11 @@ -// Tim has to complete a few chores today, before he's allowed to play soccer -// with his friends. His friends decide to help him. Working together, they -// finish the chores earlier and have more time left to play soccer. +// Alice is an elementary school teacher who needs to calculate the mean test +// score for three classes she teaches. Instead of calculating them one after +// the other, she decides to ask her friends Bob and Catherine for help. Working +// together, they can finish the job much faster. // -// Let's simulate this using asynchronous programming. Each boy is represented -// as an asynchronous task, which can be executed concurrently (they can be -// working at the same time). - -use std::sync::atomic::{AtomicU8, Ordering}; - -// Used by "mom" to check that all chores are done before Tim plays soccer :-) -static CHORES_DONE: AtomicU8 = AtomicU8::new(0); +// Let's simulate this using asynchronous programming. Each person is +// represented as an asynchronous task, which can be executed concurrently (i.e. +// they can be doing the calculations at the same time). fn main() { // Async tasks need to be executed by a "runtime", which is not provided by @@ -18,36 +14,29 @@ fn main() { .build() .unwrap(); - let task_tim = rt.spawn(tim()); - let task_carl = rt.spawn(carl()); - let task_nick = rt.spawn(nick()); - - // Block the runtime on a task that waits for all boys to finish the chores. - rt.block_on(async { - task_tim.await.unwrap(); - task_carl.await.unwrap(); - task_nick.await.unwrap(); + let scores_class_a = &[83, 77, 92]; + let scores_class_b = &[84, 88, 96]; + let scores_class_c = &[71, 83, 76]; + + let alice = rt.spawn(calculate_mean_score(scores_class_a)); + let bob = rt.spawn(calculate_mean_score(scores_class_b)); + let catherine = rt.spawn(calculate_mean_score(scores_class_c)); + + // Block the runtime on a task that awaits all three calculations. + let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async { + [ + alice.await.unwrap(), + bob.await.unwrap(), + catherine.await.unwrap(), + ] }); - assert_eq!( - CHORES_DONE.load(Ordering::SeqCst), - 3, - "Did you (a)wait for all the boys to finish the chores?" - ); - println!("Ready to play soccer!"); -} - -async fn tim() { - println!("Cleaning my room..."); - CHORES_DONE.fetch_add(1, Ordering::SeqCst); -} - -async fn carl() { - println!("Washing the dishes..."); - CHORES_DONE.fetch_add(1, Ordering::SeqCst); + assert_eq!(mean_score_a, 84); + assert_eq!(mean_score_b, 89); + assert_eq!(mean_score_c, 76); } -async fn nick() { - println!("Mowing the lawn..."); - CHORES_DONE.fetch_add(1, Ordering::SeqCst); +async fn calculate_mean_score(score_list: &[usize]) -> usize { + let score_sum: usize = score_list.iter().sum(); + score_sum / score_list.len() }