Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ bin = [
{ name = "conversions4_sol", path = "../solutions/23_conversions/conversions4.rs" },
{ name = "conversions5", path = "../exercises/23_conversions/conversions5.rs" },
{ name = "conversions5_sol", path = "../solutions/23_conversions/conversions5.rs" },
{ name = "async_await1", path = "../exercises/24_async_await/async_await1.rs" },
{ name = "async_await1_sol", path = "../solutions/24_async_await/async_await1.rs" },
{ name = "async_await2", path = "../exercises/24_async_await/async_await2.rs" },
{ name = "async_await2_sol", path = "../solutions/24_async_await/async_await2.rs" },
{ name = "async_await3", path = "../exercises/24_async_await/async_await3.rs" },
{ name = "async_await3_sol", path = "../solutions/24_async_await/async_await3.rs" },
{ name = "async_await4", path = "../exercises/24_async_await/async_await4.rs" },
{ name = "async_await4_sol", path = "../solutions/24_async_await/async_await4.rs" },
]

[package]
Expand All @@ -196,6 +204,9 @@ edition = "2024"
# Don't publish the exercises on crates.io!
publish = false

[dependencies]
trpl = "0.3.0"

[profile.release]
panic = "abort"

Expand Down
15 changes: 15 additions & 0 deletions exercises/24_async_await/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions exercises/24_async_await/async_await1.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 32 additions & 0 deletions exercises/24_async_await/async_await2.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions exercises/24_async_await/async_await3.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 93 additions & 0 deletions exercises/24_async_await/async_await4.rs
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
1 change: 1 addition & 0 deletions exercises/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
| macros | §20.5 |
| clippy | Appendix D |
| conversions | n/a |
| async_await | §17 |
54 changes: 54 additions & 0 deletions rustlings-macros/info.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 29 additions & 0 deletions solutions/24_async_await/async_await1.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 32 additions & 0 deletions solutions/24_async_await/async_await2.rs
Original file line number Diff line number Diff line change
@@ -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]) });
}
}
Loading