Skip to content

Commit 819d15b

Browse files
committed
Add exercise async2
1 parent f927cbe commit 819d15b

4 files changed

Lines changed: 200 additions & 1 deletion

File tree

dev/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ bin = [
190190
{ name = "conversions5_sol", path = "../solutions/23_conversions/conversions5.rs" },
191191
{ name = "async1", path = "../exercises/24_async/async1.rs" },
192192
{ name = "async1_sol", path = "../solutions/24_async/async1.rs" },
193+
{ name = "async2", path = "../exercises/24_async/async2.rs" },
194+
{ name = "async2_sol", path = "../solutions/24_async/async2.rs" },
193195
]
194196

195197
[package]
@@ -199,7 +201,7 @@ edition = "2024"
199201
publish = false
200202

201203
[dependencies]
202-
tokio = { version = "1.52.1", features = ["rt"] }
204+
tokio = { version = "1.52.1", features = ["rt", "sync", "time"] }
203205

204206
[profile.release]
205207
panic = "abort"

exercises/24_async/async2.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Two people are talking on the phone. One of them is telling a story. The
2+
// other one is interjecting with little acknowledgments, to show their interest
3+
// in the story.
4+
//
5+
// However, there is a problem. The phone connection is synchronous, so all
6+
// the acknowledgments from the listener arrive only at the very end of the
7+
// conversation! What the speaker and listener say should be interleaved.
8+
//
9+
// Let's use asynchronous programming to make the conversation more natural!
10+
11+
use std::time::Duration;
12+
13+
use tokio::sync::mpsc;
14+
15+
fn main() {
16+
let rt = tokio::runtime::Builder::new_current_thread()
17+
.enable_time()
18+
.build()
19+
.unwrap();
20+
let _guard = rt.enter();
21+
22+
let time_scale = Duration::from_millis(1);
23+
24+
let (speaker_phone, listener_phone, mut wire_tap) = start_wire_tapped_phone_call();
25+
26+
let speaker = async move {
27+
for msg in SPEAKER_MESSAGES {
28+
speaker_phone.say(msg).await;
29+
// wait for listener to interject
30+
wait_silently(time_scale * 2).await;
31+
}
32+
};
33+
let listener = async move {
34+
// give speaker a head-start
35+
wait_silently(time_scale * 1).await;
36+
for msg in LISTENER_MESSAGES {
37+
listener_phone.say(msg).await;
38+
// wait for speaker to continue story
39+
wait_silently(time_scale * 2).await;
40+
}
41+
};
42+
tokio::spawn(speaker);
43+
tokio::spawn(listener);
44+
45+
let messages: Vec<_> = std::iter::from_fn(|| rt.block_on(wire_tap.recv())).collect();
46+
for message in &messages {
47+
println!("{message}");
48+
}
49+
let expected = SPEAKER_MESSAGES
50+
.iter()
51+
.zip(LISTENER_MESSAGES)
52+
.flat_map(|(&a, &b)| [a, b]);
53+
for (expected, message) in expected.zip(messages) {
54+
assert_eq!(message, expected, "")
55+
}
56+
}
57+
58+
async fn wait_silently(duration: Duration) {
59+
// TODO: The sleep function from the standard library blocks the current
60+
// thread, preventing other async tasks from progressing. The tokio
61+
// library, which provides our async runtime, can help:
62+
// https://docs.rs/tokio/latest/tokio/time/fn.sleep.html
63+
std::thread::sleep(duration);
64+
}
65+
66+
const SPEAKER_MESSAGES: &[&str] = &[
67+
"> So I was walking in the park...",
68+
"> where I met Susan by coincidence...",
69+
"> and she was wearing a purple hat!",
70+
];
71+
const LISTENER_MESSAGES: &[&str] = &[
72+
" I see. <",
73+
" Oh, really? <",
74+
" No way! <",
75+
];
76+
77+
/// This phone is wire-tapped for testing purposes.
78+
#[derive(Clone)]
79+
struct Phone {
80+
sender: mpsc::Sender<&'static str>,
81+
}
82+
83+
// Create a wire-tapped phone call.
84+
fn start_wire_tapped_phone_call() -> (Phone, Phone, mpsc::Receiver<&'static str>) {
85+
let (sender, wire_tap) = mpsc::channel(6);
86+
let phone = Phone { sender };
87+
(phone.clone(), phone, wire_tap)
88+
}
89+
90+
impl Phone {
91+
/// Say something on the phone.
92+
async fn say(&self, thing: &'static str) {
93+
self.sender.send(thing).await.unwrap();
94+
}
95+
}

rustlings-macros/info.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,3 +1225,10 @@ the functions "tim", "carl" and "nick".
12251225
12261226
An async task can wait for another one to complete by "awaiting" it. Add
12271227
".await" after the three "task_name" variables in the "block_on" call."""
1228+
1229+
[[exercises]]
1230+
name = "async2"
1231+
dir = "24_async"
1232+
test = false
1233+
hint = """
1234+
TODO"""

solutions/24_async/async2.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Two people are talking on the phone. One of them is telling a story. The
2+
// other one is interjecting with little acknowledgments, to show their interest
3+
// in the story.
4+
//
5+
// However, there is a problem. The phone connection is synchronous, so all
6+
// the acknowledgments from the listener arrive only at the very end of the
7+
// conversation! What the speaker and listener say should be interleaved.
8+
//
9+
// Let's use asynchronous programming to make the conversation more natural!
10+
11+
use std::time::Duration;
12+
13+
use tokio::sync::mpsc;
14+
15+
fn main() {
16+
let rt = tokio::runtime::Builder::new_current_thread()
17+
.enable_time()
18+
.build()
19+
.unwrap();
20+
let _guard = rt.enter();
21+
22+
let time_scale = Duration::from_millis(1);
23+
24+
let (speaker_phone, listener_phone, mut wire_tap) = start_wire_tapped_phone_call();
25+
26+
let speaker = async move {
27+
for msg in SPEAKER_MESSAGES {
28+
speaker_phone.say(msg).await;
29+
// wait for listener to interject
30+
wait_silently(time_scale * 2).await;
31+
}
32+
};
33+
let listener = async move {
34+
// give speaker a head-start
35+
wait_silently(time_scale * 1).await;
36+
for msg in LISTENER_MESSAGES {
37+
listener_phone.say(msg).await;
38+
// wait for speaker to continue story
39+
wait_silently(time_scale * 2).await;
40+
}
41+
};
42+
tokio::spawn(speaker);
43+
tokio::spawn(listener);
44+
45+
let messages: Vec<_> = std::iter::from_fn(|| rt.block_on(wire_tap.recv())).collect();
46+
for message in &messages {
47+
println!("{message}");
48+
}
49+
let expected = SPEAKER_MESSAGES
50+
.iter()
51+
.zip(LISTENER_MESSAGES)
52+
.flat_map(|(&a, &b)| [a, b]);
53+
for (expected, message) in expected.zip(messages) {
54+
assert_eq!(message, expected, "")
55+
}
56+
}
57+
58+
async fn wait_silently(duration: Duration) {
59+
// TODO: The sleep function from the standard library blocks the current
60+
// thread, preventing other async tasks from progressing. The tokio
61+
// library, which provides our async runtime, can help:
62+
// https://docs.rs/tokio/latest/tokio/time/fn.sleep.html
63+
tokio::time::sleep(duration).await;
64+
}
65+
66+
const SPEAKER_MESSAGES: &[&str] = &[
67+
"> So I was walking in the park...",
68+
"> where I met Susan by coincidence...",
69+
"> and she was wearing a purple hat!",
70+
];
71+
const LISTENER_MESSAGES: &[&str] = &[
72+
" I see. <",
73+
" Oh, really? <",
74+
" No way! <",
75+
];
76+
77+
/// This phone is wire-tapped for testing purposes.
78+
#[derive(Clone)]
79+
struct Phone {
80+
sender: mpsc::Sender<&'static str>,
81+
}
82+
83+
// Create a wire-tapped phone call.
84+
fn start_wire_tapped_phone_call() -> (Phone, Phone, mpsc::Receiver<&'static str>) {
85+
let (sender, wire_tap) = mpsc::channel(6);
86+
let phone = Phone { sender };
87+
(phone.clone(), phone, wire_tap)
88+
}
89+
90+
impl Phone {
91+
/// Say something on the phone.
92+
async fn say(&self, thing: &'static str) {
93+
self.sender.send(thing).await.unwrap();
94+
}
95+
}

0 commit comments

Comments
 (0)