mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-05-17 18:58:45 +00:00
Merge 09cd4bd50894ae999fc1080f51ac4c264f80fed6 into e38c82ccbb92829204c1a9dd5e01baf09c6af1e2
This commit is contained in:
commit
ba869466a6
@ -188,6 +188,10 @@ bin = [
|
|||||||
{ name = "try_from_into_sol", path = "../solutions/23_conversions/try_from_into.rs" },
|
{ 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", path = "../exercises/23_conversions/as_ref_mut.rs" },
|
||||||
{ name = "as_ref_mut_sol", path = "../solutions/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" },
|
||||||
|
{ name = "async2", path = "../exercises/24_async/async2.rs" },
|
||||||
|
{ name = "async2_sol", path = "../solutions/24_async/async2.rs" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
@ -196,6 +200,9 @@ edition = "2024"
|
|||||||
# Don't publish the exercises on crates.io!
|
# Don't publish the exercises on crates.io!
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.52.1", features = ["rt", "sync", "time"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
|
|||||||
13
exercises/24_async/README.md
Normal file
13
exercises/24_async/README.md
Normal file
@ -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/)
|
||||||
55
exercises/24_async/async1.rs
Normal file
55
exercises/24_async/async1.rs
Normal file
@ -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);
|
||||||
|
}
|
||||||
95
exercises/24_async/async2.rs
Normal file
95
exercises/24_async/async2.rs
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1205,3 +1205,24 @@ name = "as_ref_mut"
|
|||||||
dir = "23_conversions"
|
dir = "23_conversions"
|
||||||
hint = """
|
hint = """
|
||||||
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""
|
Add `AsRef<str>` or `AsMut<u32>` 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."""
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "async2"
|
||||||
|
dir = "24_async"
|
||||||
|
test = false
|
||||||
|
hint = """
|
||||||
|
TODO"""
|
||||||
|
|||||||
53
solutions/24_async/async1.rs
Normal file
53
solutions/24_async/async1.rs
Normal file
@ -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);
|
||||||
|
}
|
||||||
95
solutions/24_async/async2.rs
Normal file
95
solutions/24_async/async2.rs
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user