mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-06-30 08:18:45 +00:00
add async-await exercises
This commit is contained in:
parent
4bab596677
commit
30c8300074
@ -196,6 +196,9 @@ edition = "2024"
|
|||||||
# Don't publish the exercises on crates.io!
|
# Don't publish the exercises on crates.io!
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
trpl = "0.3.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
|
|||||||
15
exercises/24_async_await/README.md
Normal file
15
exercises/24_async_await/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Async/Await
|
||||||
|
|
||||||
|
Rust's async model lets you write concurrent code that waits for I/O or other
|
||||||
|
work without blocking a thread. An `async fn` returns a `Future` — a value that
|
||||||
|
represents work still in progress. The `.await` keyword pauses until that work
|
||||||
|
finishes.
|
||||||
|
|
||||||
|
Futures don't run on their own: an **async runtime** (such as [Tokio](https://tokio.rs))
|
||||||
|
polls them to completion. Rustlings uses Tokio (via the [`trpl`](https://crates.io/crates/trpl) crate) for these exercises.
|
||||||
|
|
||||||
|
## Further information
|
||||||
|
|
||||||
|
- [The Rust Programming Language: Async Programming](https://doc.rust-lang.org/book/ch17-00-async-await.html)
|
||||||
|
- [Async Programming Book](https://rust-lang.github.io/async-book/)
|
||||||
|
- [Tokio tutorial](https://tokio.rs/tokio/tutorial)
|
||||||
27
exercises/24_async_await/async_await1.rs
Normal file
27
exercises/24_async_await/async_await1.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// This program fetches a greeting asynchronously. Async functions return a
|
||||||
|
// `Future` that must be `.await`ed to get the result. An async runtime like
|
||||||
|
// Tokio is needed to drive futures to completion.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
async fn fetch_greeting() -> String {
|
||||||
|
trpl::sleep(Duration::from_millis(10)).await;
|
||||||
|
String::from("Hello, async world!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
// TODO: We need to `await` the future returned by `fetch_greeting`.
|
||||||
|
let greeting = fetch_greeting();
|
||||||
|
println!("{greeting}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fetch_greeting_returns_expected_message() {
|
||||||
|
trpl::block_on(async { assert_eq!(fetch_greeting().await, "Hello, async world!") });
|
||||||
|
}
|
||||||
|
}
|
||||||
32
exercises/24_async_await/async_await2.rs
Normal file
32
exercises/24_async_await/async_await2.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// This program runs multiple async operations. The `Future` trait represents an
|
||||||
|
// asynchronous computation. Multiple futures can be polled concurrently with
|
||||||
|
// macros like `trpl::join!`.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
async fn compute_value(id: u32) -> u32 {
|
||||||
|
trpl::sleep(Duration::from_millis(10)).await;
|
||||||
|
id * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn compute_all() -> Vec<u32> {
|
||||||
|
// TODO: Use `trpl::join!` to await three futures concurrently.
|
||||||
|
// Collect the results into a vector, e.g. `[10, 20, 30]`.
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
let results = compute_all().await;
|
||||||
|
println!("Computed: {results:?}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_all_returns_expected_values() {
|
||||||
|
trpl::block_on(async { assert_eq!(compute_all().await, [10, 20, 30]) });
|
||||||
|
}
|
||||||
|
}
|
||||||
42
exercises/24_async_await/async_await3.rs
Normal file
42
exercises/24_async_await/async_await3.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Async runtimes like Tokio can spawn independent tasks that run concurrently
|
||||||
|
// on the runtime's thread pool. Each spawned task returns a `JoinHandle` that
|
||||||
|
// can be `.await`ed to get its result — similar to `thread::spawn` and `join`.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
async fn double(n: u32) -> u32 {
|
||||||
|
trpl::sleep(Duration::from_millis(10)).await;
|
||||||
|
n * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn double_all(values: &[u32]) -> Vec<u32> {
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
for &value in values {
|
||||||
|
// TODO: Spawn `double(value)` on the Tokio runtime using `trpl::spawn_task`.
|
||||||
|
// Push the returned `JoinHandle` into `handles`.
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for handle in handles {
|
||||||
|
// TODO: Await each spawned task and collect its result into `results`.
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
let results = double_all(&[1, 2, 3, 4, 5]).await;
|
||||||
|
println!("Results: {results:?}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_all_double_values() {
|
||||||
|
trpl::block_on(async { assert_eq!(double_all(&[1, 2, 3, 4, 5]).await, [2, 4, 6, 8, 10]) });
|
||||||
|
}
|
||||||
|
}
|
||||||
93
exercises/24_async_await/async_await4.rs
Normal file
93
exercises/24_async_await/async_await4.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// This exercise demonstrates async message-passing concurrency using a channel.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
struct Queue {
|
||||||
|
first_half: Vec<String>,
|
||||||
|
second_half: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Queue {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
first_half: vec![
|
||||||
|
String::from("winter"),
|
||||||
|
String::from("is"),
|
||||||
|
String::from("really"),
|
||||||
|
String::from("coming"),
|
||||||
|
],
|
||||||
|
second_half: vec![
|
||||||
|
String::from("we"),
|
||||||
|
String::from("do"),
|
||||||
|
String::from("not"),
|
||||||
|
String::from("sow"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transmit(q: Queue, tx: trpl::Sender<String>) {
|
||||||
|
// TODO: We want to send `tx` to both tasks. But currently, it is moved
|
||||||
|
// into the first task (future). What change do we need to make in order to
|
||||||
|
// solve for this issue?
|
||||||
|
|
||||||
|
let tx_fut1 = async move {
|
||||||
|
for val in q.first_half {
|
||||||
|
tx.send(val).unwrap();
|
||||||
|
trpl::sleep(Duration::from_millis(250)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_fut2 = async move {
|
||||||
|
for val in q.second_half {
|
||||||
|
tx.send(val).unwrap();
|
||||||
|
trpl::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trpl::join(tx_fut1, tx_fut2).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
let (tx, mut rx) = trpl::channel();
|
||||||
|
|
||||||
|
let queue = Queue::new();
|
||||||
|
|
||||||
|
let tx_fut = transmit(queue, tx);
|
||||||
|
|
||||||
|
// TODO: Define an `rx_fut` async block that loops through all received values from `rx`
|
||||||
|
// and processes each one.
|
||||||
|
|
||||||
|
// TODO: Wait on the two futures (`tx_fut` and `rx_fut`) to complete.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_messages_are_received() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
let (tx, mut rx) = trpl::channel();
|
||||||
|
let queue = Queue::new();
|
||||||
|
|
||||||
|
transmit(queue, tx).await;
|
||||||
|
|
||||||
|
let mut received = Vec::new();
|
||||||
|
while let Some(val) = rx.recv().await {
|
||||||
|
received.push(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
received.sort();
|
||||||
|
|
||||||
|
let expected: Vec<String> =
|
||||||
|
vec!["coming", "do", "is", "not", "really", "sow", "we", "winter"]
|
||||||
|
.into_iter()
|
||||||
|
.map(String::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_eq!(received, expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,3 +25,4 @@
|
|||||||
| macros | §20.5 |
|
| macros | §20.5 |
|
||||||
| clippy | Appendix D |
|
| clippy | Appendix D |
|
||||||
| conversions | n/a |
|
| conversions | n/a |
|
||||||
|
| async_await | §17 |
|
||||||
|
|||||||
@ -1211,3 +1211,57 @@ name = "conversions5"
|
|||||||
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/AWAIT
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "async_await1"
|
||||||
|
dir = "24_async_await"
|
||||||
|
hint = """
|
||||||
|
An `async fn` returns a `Future`, not the final value. Use `.await` to run the
|
||||||
|
future to completion and get the result.
|
||||||
|
|
||||||
|
Rust doesn't ship with an async runtime in the standard library. `Tokio` is a popular
|
||||||
|
async runtime for Rust that provides an event loop and task scheduling.
|
||||||
|
The `trpl::block_on()` function runs a single future to completion on the Tokio `Runtime`.
|
||||||
|
Every time it's called, a new instance of `tokio::runtime::Runtime` is created.
|
||||||
|
|
||||||
|
See the Async Programming chapter of the Rust book:
|
||||||
|
https://doc.rust-lang.org/book/ch17-01-futures-and-syntax.html"""
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "async_await2"
|
||||||
|
dir = "24_async_await"
|
||||||
|
hint = """
|
||||||
|
`trpl::join!` takes multiple futures and polls them concurrently. It returns a
|
||||||
|
tuple with all results once every future has completed.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```rust
|
||||||
|
let (a, b) = trpl::join!(future_a(), future_b());
|
||||||
|
```
|
||||||
|
|
||||||
|
Resource: https://doc.rust-lang.org/book/ch17-02-concurrency-with-async.html"""
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "async_await3"
|
||||||
|
dir = "24_async_await"
|
||||||
|
hint = """
|
||||||
|
Use `trpl::spawn_task` to run a future as an independent task on the runtime.
|
||||||
|
It returns a `JoinHandle` which you can `.await` to get the task's output.
|
||||||
|
|
||||||
|
This is the async equivalent of spawning threads and calling `join` on them.
|
||||||
|
|
||||||
|
Resource: https://doc.rust-lang.org/book/ch17-02-concurrency-with-async.html"""
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "async_await4"
|
||||||
|
dir = "24_async_await"
|
||||||
|
hint = """
|
||||||
|
The async channel is a multiple-producer channel, so we can send multiple messages
|
||||||
|
from multiple futures.
|
||||||
|
|
||||||
|
Then we can use `trpl::join!` to await multiple futures to complete. `trpl::join!`
|
||||||
|
cooperatively polls futures on the same executor thread.
|
||||||
|
|
||||||
|
Resource: https://doc.rust-lang.org/book/ch17-02-concurrency-with-async.html"""
|
||||||
|
|||||||
29
solutions/24_async_await/async_await1.rs
Normal file
29
solutions/24_async_await/async_await1.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// This program fetches a greeting asynchronously. Async functions return a
|
||||||
|
// `Future` that must be `.await`ed to get the result. An async runtime like
|
||||||
|
// Tokio is needed to drive futures to completion.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
async fn fetch_greeting() -> String {
|
||||||
|
trpl::sleep(Duration::from_millis(10)).await;
|
||||||
|
String::from("Hello, async world!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// `trpl::block_on()` runs a single future to completion on the Tokio `Runtime`.
|
||||||
|
// Every time it's called, a new instance of `tokio::runtime::Runtime` will be created.
|
||||||
|
trpl::block_on(async {
|
||||||
|
// `.await` suspends until the future completes and yields the `String`.
|
||||||
|
let greeting = fetch_greeting().await;
|
||||||
|
println!("{greeting}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fetch_greeting_returns_expected_message() {
|
||||||
|
trpl::block_on(async { assert_eq!(fetch_greeting().await, "Hello, async world!") });
|
||||||
|
}
|
||||||
|
}
|
||||||
32
solutions/24_async_await/async_await2.rs
Normal file
32
solutions/24_async_await/async_await2.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// This program runs multiple async operations. The `Future` trait represents an
|
||||||
|
// asynchronous computation. Multiple futures can be polled concurrently with
|
||||||
|
// macros like `trpl::join!`.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
async fn compute_value(id: u32) -> u32 {
|
||||||
|
trpl::sleep(Duration::from_millis(10)).await;
|
||||||
|
id * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn compute_all() -> Vec<u32> {
|
||||||
|
// `trpl::join!` polls all futures concurrently and returns a tuple of results.
|
||||||
|
let (a, b, c) = trpl::join!(compute_value(1), compute_value(2), compute_value(3));
|
||||||
|
vec![a, b, c]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
let results = compute_all().await;
|
||||||
|
println!("Computed: {results:?}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_all_returns_expected_values() {
|
||||||
|
trpl::block_on(async { assert_eq!(compute_all().await, [10, 20, 30]) });
|
||||||
|
}
|
||||||
|
}
|
||||||
42
solutions/24_async_await/async_await3.rs
Normal file
42
solutions/24_async_await/async_await3.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Async runtimes like Tokio can spawn independent tasks that run concurrently
|
||||||
|
// on the runtime's thread pool. Each spawned task returns a `JoinHandle` that
|
||||||
|
// can be `.await`ed to get its result — similar to `thread::spawn` and `join`.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
async fn double(n: u32) -> u32 {
|
||||||
|
trpl::sleep(Duration::from_millis(10)).await;
|
||||||
|
n * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn double_all(values: &[u32]) -> Vec<u32> {
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
for &value in values {
|
||||||
|
// `trpl::spawn_task` schedules the future on the runtime's executor.
|
||||||
|
handles.push(trpl::spawn_task(double(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for handle in handles {
|
||||||
|
// Awaiting the `JoinHandle` waits for the spawned task to finish.
|
||||||
|
results.push(handle.await.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
let results = double_all(&[1, 2, 3, 4, 5]).await;
|
||||||
|
println!("Results: {results:?}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compute_all_double_values() {
|
||||||
|
trpl::block_on(async { assert_eq!(double_all(&[1, 2, 3, 4, 5]).await, [2, 4, 6, 8, 10]) });
|
||||||
|
}
|
||||||
|
}
|
||||||
97
solutions/24_async_await/async_await4.rs
Normal file
97
solutions/24_async_await/async_await4.rs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// This exercise demonstrates async message-passing concurrency using a channel.
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
struct Queue {
|
||||||
|
first_half: Vec<String>,
|
||||||
|
second_half: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Queue {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
first_half: vec![
|
||||||
|
String::from("winter"),
|
||||||
|
String::from("is"),
|
||||||
|
String::from("really"),
|
||||||
|
String::from("coming"),
|
||||||
|
],
|
||||||
|
second_half: vec![
|
||||||
|
String::from("we"),
|
||||||
|
String::from("do"),
|
||||||
|
String::from("not"),
|
||||||
|
String::from("sow"),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transmit(q: Queue, tx: trpl::Sender<String>) {
|
||||||
|
// Clone the sender `tx` first.
|
||||||
|
let tx_clone = tx.clone();
|
||||||
|
|
||||||
|
let tx_fut1 = async move {
|
||||||
|
for val in q.first_half {
|
||||||
|
// Then we use the clone here
|
||||||
|
tx_clone.send(val).unwrap();
|
||||||
|
trpl::sleep(Duration::from_millis(250)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_fut2 = async move {
|
||||||
|
for val in q.second_half {
|
||||||
|
// And here we use the original sender `tx`
|
||||||
|
tx.send(val).unwrap();
|
||||||
|
trpl::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trpl::join(tx_fut1, tx_fut2).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
let (tx, mut rx) = trpl::channel();
|
||||||
|
|
||||||
|
let queue = Queue::new();
|
||||||
|
|
||||||
|
let tx_fut = transmit(queue, tx);
|
||||||
|
|
||||||
|
let rx_fut = async {
|
||||||
|
while let Some(value) = rx.recv().await {
|
||||||
|
println!("Received: {value:?}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trpl::join!(tx_fut, rx_fut); // OR `trpl::join(tx_fut, rx_fut).await`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_messages_are_received() {
|
||||||
|
trpl::block_on(async {
|
||||||
|
let (tx, mut rx) = trpl::channel();
|
||||||
|
let queue = Queue::new();
|
||||||
|
|
||||||
|
transmit(queue, tx).await;
|
||||||
|
|
||||||
|
let mut received = Vec::new();
|
||||||
|
while let Some(val) = rx.recv().await {
|
||||||
|
received.push(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
received.sort();
|
||||||
|
|
||||||
|
let expected: Vec<String> =
|
||||||
|
vec!["coming", "do", "is", "not", "really", "sow", "we", "winter"]
|
||||||
|
.into_iter()
|
||||||
|
.map(String::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_eq!(received, expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user