From 09cd4bd50894ae999fc1080f51ac4c264f80fed6 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 18 Apr 2026 23:32:07 +0200 Subject: [PATCH] Add exercise async2 --- dev/Cargo.toml | 4 +- exercises/24_async/async2.rs | 95 ++++++++++++++++++++++++++++++++++++ rustlings-macros/info.toml | 7 +++ solutions/24_async/async2.rs | 95 ++++++++++++++++++++++++++++++++++++ 4 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 exercises/24_async/async2.rs create mode 100644 solutions/24_async/async2.rs diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 3583e59d..234b1c50 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -190,6 +190,8 @@ bin = [ { 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" }, + { name = "async2", path = "../exercises/24_async/async2.rs" }, + { name = "async2_sol", path = "../solutions/24_async/async2.rs" }, ] [package] @@ -199,7 +201,7 @@ edition = "2024" publish = false [dependencies] -tokio = { version = "1.52.1", features = ["rt"] } +tokio = { version = "1.52.1", features = ["rt", "sync", "time"] } [profile.release] panic = "abort" diff --git a/exercises/24_async/async2.rs b/exercises/24_async/async2.rs new file mode 100644 index 00000000..bdaf30c4 --- /dev/null +++ b/exercises/24_async/async2.rs @@ -0,0 +1,95 @@ +// Two people are talking on the phone. One of them is telling a story. The +// other one is interjecting with little acknowledgments, to show their interest +// in the story. +// +// However, there is a problem. The phone connection is synchronous, so all +// the acknowledgments from the listener arrive only at the very end of the +// conversation! What the speaker and listener say should be interleaved. +// +// Let's use asynchronous programming to make the conversation more natural! + +use std::time::Duration; + +use tokio::sync::mpsc; + +fn main() { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .unwrap(); + let _guard = rt.enter(); + + let time_scale = Duration::from_millis(1); + + let (speaker_phone, listener_phone, mut wire_tap) = start_wire_tapped_phone_call(); + + let speaker = async move { + for msg in SPEAKER_MESSAGES { + speaker_phone.say(msg).await; + // wait for listener to interject + wait_silently(time_scale * 2).await; + } + }; + let listener = async move { + // give speaker a head-start + wait_silently(time_scale * 1).await; + for msg in LISTENER_MESSAGES { + listener_phone.say(msg).await; + // wait for speaker to continue story + wait_silently(time_scale * 2).await; + } + }; + tokio::spawn(speaker); + tokio::spawn(listener); + + let messages: Vec<_> = std::iter::from_fn(|| rt.block_on(wire_tap.recv())).collect(); + for message in &messages { + println!("{message}"); + } + let expected = SPEAKER_MESSAGES + .iter() + .zip(LISTENER_MESSAGES) + .flat_map(|(&a, &b)| [a, b]); + for (expected, message) in expected.zip(messages) { + assert_eq!(message, expected, "") + } +} + +async fn wait_silently(duration: Duration) { + // TODO: The sleep function from the standard library blocks the current + // thread, preventing other async tasks from progressing. The tokio + // library, which provides our async runtime, can help: + // https://docs.rs/tokio/latest/tokio/time/fn.sleep.html + std::thread::sleep(duration); +} + +const SPEAKER_MESSAGES: &[&str] = &[ + "> So I was walking in the park...", + "> where I met Susan by coincidence...", + "> and she was wearing a purple hat!", +]; +const LISTENER_MESSAGES: &[&str] = &[ + " I see. <", + " Oh, really? <", + " No way! <", +]; + +/// This phone is wire-tapped for testing purposes. +#[derive(Clone)] +struct Phone { + sender: mpsc::Sender<&'static str>, +} + +// Create a wire-tapped phone call. +fn start_wire_tapped_phone_call() -> (Phone, Phone, mpsc::Receiver<&'static str>) { + let (sender, wire_tap) = mpsc::channel(6); + let phone = Phone { sender }; + (phone.clone(), phone, wire_tap) +} + +impl Phone { + /// Say something on the phone. + async fn say(&self, thing: &'static str) { + self.sender.send(thing).await.unwrap(); + } +} diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 8266ea99..84b6e5aa 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -1213,3 +1213,10 @@ 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.""" + +[[exercises]] +name = "async2" +dir = "24_async" +test = false +hint = """ +TODO""" diff --git a/solutions/24_async/async2.rs b/solutions/24_async/async2.rs new file mode 100644 index 00000000..c7ef6b8a --- /dev/null +++ b/solutions/24_async/async2.rs @@ -0,0 +1,95 @@ +// Two people are talking on the phone. One of them is telling a story. The +// other one is interjecting with little acknowledgments, to show their interest +// in the story. +// +// However, there is a problem. The phone connection is synchronous, so all +// the acknowledgments from the listener arrive only at the very end of the +// conversation! What the speaker and listener say should be interleaved. +// +// Let's use asynchronous programming to make the conversation more natural! + +use std::time::Duration; + +use tokio::sync::mpsc; + +fn main() { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .unwrap(); + let _guard = rt.enter(); + + let time_scale = Duration::from_millis(1); + + let (speaker_phone, listener_phone, mut wire_tap) = start_wire_tapped_phone_call(); + + let speaker = async move { + for msg in SPEAKER_MESSAGES { + speaker_phone.say(msg).await; + // wait for listener to interject + wait_silently(time_scale * 2).await; + } + }; + let listener = async move { + // give speaker a head-start + wait_silently(time_scale * 1).await; + for msg in LISTENER_MESSAGES { + listener_phone.say(msg).await; + // wait for speaker to continue story + wait_silently(time_scale * 2).await; + } + }; + tokio::spawn(speaker); + tokio::spawn(listener); + + let messages: Vec<_> = std::iter::from_fn(|| rt.block_on(wire_tap.recv())).collect(); + for message in &messages { + println!("{message}"); + } + let expected = SPEAKER_MESSAGES + .iter() + .zip(LISTENER_MESSAGES) + .flat_map(|(&a, &b)| [a, b]); + for (expected, message) in expected.zip(messages) { + assert_eq!(message, expected, "") + } +} + +async fn wait_silently(duration: Duration) { + // TODO: The sleep function from the standard library blocks the current + // thread, preventing other async tasks from progressing. The tokio + // library, which provides our async runtime, can help: + // https://docs.rs/tokio/latest/tokio/time/fn.sleep.html + tokio::time::sleep(duration).await; +} + +const SPEAKER_MESSAGES: &[&str] = &[ + "> So I was walking in the park...", + "> where I met Susan by coincidence...", + "> and she was wearing a purple hat!", +]; +const LISTENER_MESSAGES: &[&str] = &[ + " I see. <", + " Oh, really? <", + " No way! <", +]; + +/// This phone is wire-tapped for testing purposes. +#[derive(Clone)] +struct Phone { + sender: mpsc::Sender<&'static str>, +} + +// Create a wire-tapped phone call. +fn start_wire_tapped_phone_call() -> (Phone, Phone, mpsc::Receiver<&'static str>) { + let (sender, wire_tap) = mpsc::channel(6); + let phone = Phone { sender }; + (phone.clone(), phone, wire_tap) +} + +impl Phone { + /// Say something on the phone. + async fn say(&self, thing: &'static str) { + self.sender.send(thing).await.unwrap(); + } +}