diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 4f725b70..3583e59d 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 00000000..7928650e --- /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 00000000..5810ced3 --- /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 353d405c..8266ea99 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 00000000..a897067a --- /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); +}