mirror of
https://github.com/rust-lang/rustlings.git
synced 2026-06-30 00:08: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!
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
trpl = "0.3.0"
|
||||
|
||||
[profile.release]
|
||||
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 |
|
||||
| clippy | Appendix D |
|
||||
| conversions | n/a |
|
||||
| async_await | §17 |
|
||||
|
||||
@ -1211,3 +1211,57 @@ name = "conversions5"
|
||||
dir = "23_conversions"
|
||||
hint = """
|
||||
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