Skip to content

Commit 9b06f59

Browse files
committed
feat: add async exercises
1 parent e73fff3 commit 9b06f59

35 files changed

+268
-36
lines changed

dev/Cargo.toml

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -164,30 +164,34 @@ bin = [
164164
{ name = "threads2_sol", path = "../solutions/20_threads/threads2.rs" },
165165
{ name = "threads3", path = "../exercises/20_threads/threads3.rs" },
166166
{ name = "threads3_sol", path = "../solutions/20_threads/threads3.rs" },
167-
{ name = "macros1", path = "../exercises/21_macros/macros1.rs" },
168-
{ name = "macros1_sol", path = "../solutions/21_macros/macros1.rs" },
169-
{ name = "macros2", path = "../exercises/21_macros/macros2.rs" },
170-
{ name = "macros2_sol", path = "../solutions/21_macros/macros2.rs" },
171-
{ name = "macros3", path = "../exercises/21_macros/macros3.rs" },
172-
{ name = "macros3_sol", path = "../solutions/21_macros/macros3.rs" },
173-
{ name = "macros4", path = "../exercises/21_macros/macros4.rs" },
174-
{ name = "macros4_sol", path = "../solutions/21_macros/macros4.rs" },
175-
{ name = "clippy1", path = "../exercises/22_clippy/clippy1.rs" },
176-
{ name = "clippy1_sol", path = "../solutions/22_clippy/clippy1.rs" },
177-
{ name = "clippy2", path = "../exercises/22_clippy/clippy2.rs" },
178-
{ name = "clippy2_sol", path = "../solutions/22_clippy/clippy2.rs" },
179-
{ name = "clippy3", path = "../exercises/22_clippy/clippy3.rs" },
180-
{ name = "clippy3_sol", path = "../solutions/22_clippy/clippy3.rs" },
181-
{ name = "using_as", path = "../exercises/23_conversions/using_as.rs" },
182-
{ name = "using_as_sol", path = "../solutions/23_conversions/using_as.rs" },
183-
{ name = "from_into", path = "../exercises/23_conversions/from_into.rs" },
184-
{ name = "from_into_sol", path = "../solutions/23_conversions/from_into.rs" },
185-
{ name = "from_str", path = "../exercises/23_conversions/from_str.rs" },
186-
{ name = "from_str_sol", path = "../solutions/23_conversions/from_str.rs" },
187-
{ name = "try_from_into", path = "../exercises/23_conversions/try_from_into.rs" },
188-
{ name = "try_from_into_sol", path = "../solutions/23_conversions/try_from_into.rs" },
189-
{ name = "as_ref_mut", path = "../exercises/23_conversions/as_ref_mut.rs" },
190-
{ name = "as_ref_mut_sol", path = "../solutions/23_conversions/as_ref_mut.rs" },
167+
{ name = "async1", path = "../exercises/21_async/async1.rs" },
168+
{ name = "async1_sol", path = "../solutions/21_async/async1.rs" },
169+
{ name = "async2", path = "../exercises/21_async/async2.rs" },
170+
{ name = "async2_sol", path = "../solutions/21_async/async2.rs" },
171+
{ name = "macros1", path = "../exercises/22_macros/macros1.rs" },
172+
{ name = "macros1_sol", path = "../solutions/22_macros/macros1.rs" },
173+
{ name = "macros2", path = "../exercises/22_macros/macros2.rs" },
174+
{ name = "macros2_sol", path = "../solutions/22_macros/macros2.rs" },
175+
{ name = "macros3", path = "../exercises/22_macros/macros3.rs" },
176+
{ name = "macros3_sol", path = "../solutions/22_macros/macros3.rs" },
177+
{ name = "macros4", path = "../exercises/22_macros/macros4.rs" },
178+
{ name = "macros4_sol", path = "../solutions/22_macros/macros4.rs" },
179+
{ name = "clippy1", path = "../exercises/23_clippy/clippy1.rs" },
180+
{ name = "clippy1_sol", path = "../solutions/23_clippy/clippy1.rs" },
181+
{ name = "clippy2", path = "../exercises/23_clippy/clippy2.rs" },
182+
{ name = "clippy2_sol", path = "../solutions/23_clippy/clippy2.rs" },
183+
{ name = "clippy3", path = "../exercises/23_clippy/clippy3.rs" },
184+
{ name = "clippy3_sol", path = "../solutions/23_clippy/clippy3.rs" },
185+
{ name = "using_as", path = "../exercises/24_conversions/using_as.rs" },
186+
{ name = "using_as_sol", path = "../solutions/24_conversions/using_as.rs" },
187+
{ name = "from_into", path = "../exercises/24_conversions/from_into.rs" },
188+
{ name = "from_into_sol", path = "../solutions/24_conversions/from_into.rs" },
189+
{ name = "from_str", path = "../exercises/24_conversions/from_str.rs" },
190+
{ name = "from_str_sol", path = "../solutions/24_conversions/from_str.rs" },
191+
{ name = "try_from_into", path = "../exercises/24_conversions/try_from_into.rs" },
192+
{ name = "try_from_into_sol", path = "../solutions/24_conversions/try_from_into.rs" },
193+
{ name = "as_ref_mut", path = "../exercises/24_conversions/as_ref_mut.rs" },
194+
{ name = "as_ref_mut_sol", path = "../solutions/24_conversions/as_ref_mut.rs" },
191195
]
192196

193197
[package]
@@ -196,6 +200,9 @@ edition = "2024"
196200
# Don't publish the exercises on crates.io!
197201
publish = false
198202

203+
[dependencies]
204+
tokio = { version = "1.45.0", features = ["rt-multi-thread", "macros"] }
205+
199206
[profile.release]
200207
panic = "abort"
201208

exercises/21_async/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Async
2+
3+
Rust includes built-in support for asynchronous programming. In other languages, this might be known as Promises or
4+
Coroutines. async programming uses async functions, which are powerful, but may require some getting used to,
5+
especially if you haven't used something similar in another language.
6+
7+
The [relevant book chapter][1] is essential reading. The [tokio docs][2] are also very helpful!
8+
9+
[1]: https://doc.rust-lang.org/book/ch17-00-async-await.html
10+
[2]: https://tokio.rs/tokio/tutorial

exercises/21_async/async1.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Our loyal worker works hard to create a new number.
2+
#[derive(Default)]
3+
struct Worker;
4+
5+
struct NumberContainer {
6+
number: i32,
7+
}
8+
9+
impl Worker {
10+
async fn work(&self) -> NumberContainer {
11+
// Pretend this takes a while...
12+
let new_number = 32;
13+
NumberContainer { number: new_number }
14+
}
15+
}
16+
17+
impl NumberContainer {
18+
async fn extract_number(&self) -> i32 {
19+
// And this too...
20+
self.number
21+
}
22+
}
23+
24+
// TODO: Fix the function signature!
25+
fn run_worker() -> i32 {
26+
// TODO: Make our worker create a new number and return it.
27+
}
28+
29+
fn main() {
30+
// Feel free to experiment here. You may need to make some adjustments
31+
// to this function, though.
32+
}
33+
34+
mod tests {
35+
use super::*;
36+
37+
// Don't worry about this attribute for now.
38+
// If you want to know what this does, read the hint!
39+
#[tokio::test]
40+
async fn test_if_it_works() {
41+
let number = run_worker().await;
42+
assert_eq!(number, 32);
43+
}
44+
}

exercises/21_async/async2.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use tokio::task::JoinSet;
2+
3+
// A MultiWorker can work with the power of 5 normal workers,
4+
// allowing us to create 5 new numbers at once!
5+
struct MultiWorker;
6+
7+
impl MultiWorker {
8+
async fn start_work(&self) -> JoinSet<i32> {
9+
let mut set = JoinSet::new();
10+
11+
for i in 30..35 {
12+
// TODO: `set.spawn` accepts an async function that will return the number
13+
// we want. Implement this function as a closure!
14+
set.spawn(???);
15+
}
16+
17+
set
18+
}
19+
}
20+
21+
async fn run_multi_worker() -> Vec<i32> {
22+
let tasks = MultiWorker.start_work().await;
23+
24+
// TODO: We have a bunch of tasks, how do we run them to completion
25+
// to get at the i32s they create?
26+
}
27+
28+
fn main() {
29+
// Feel free to experiment here. You may need to make some adjustments
30+
// to this function, though.
31+
}
32+
33+
mod tests {
34+
use super::*;
35+
36+
#[tokio::test]
37+
async fn test_if_it_works() {
38+
let mut numbers = run_multi_worker().await;
39+
numbers.sort(); // in case tasks run out-of-order
40+
assert_eq!(numbers, vec![30, 31, 32, 33, 34]);
41+
}
42+
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

exercises/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
| iterators | §13.2-4 |
2323
| smart_pointers | §15, §16.3 |
2424
| threads | §16.1-3 |
25+
| async | §17 |
2526
| macros | §19.5 |
2627
| clippy | §21.4 |
2728
| conversions | n/a |

rustlings-macros/info.toml

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,19 +1079,59 @@ original sending end.
10791079
Related section in The Book:
10801080
https://doc.rust-lang.org/book/ch16-02-message-passing.html"""
10811081

1082+
# ASYNC
1083+
1084+
[[exercises]]
1085+
name = "async1"
1086+
dir = "21_async"
1087+
test = true
1088+
hint = """
1089+
Async functions are not the same as normal functions -- they have to be marked with a
1090+
special bit of syntax, `async fn`. These functions don't immediately return or even
1091+
execute, you have to encourage them to do so by calling another special bit of syntax
1092+
on them.
1093+
1094+
Another thing - an async function can't be properly called in a normal function. Think of
1095+
it as something contagious -- everything that it touches needs to be marked as such. Keeping
1096+
that in mind, what adjustment do you need to make to the test function signature?
1097+
1098+
Aside:
1099+
`#[tokio::test]` (and `#[tokio::main]`) are "magic" attributes that automatically
1100+
set up what we call an async runtime. The Rust compiler intentionally doesn't supply
1101+
a default implementation of this runtime. Tokio is by far the most popular
1102+
community-developed runtime, and this macro does a lot of the heavy lifting to let
1103+
us use it.
1104+
"""
1105+
1106+
[[exercises]]
1107+
name = "async2"
1108+
dir = "21_async"
1109+
test = true
1110+
hint = """
1111+
Async functions can be used to run multiple things in parallel, or to more efficiently
1112+
run things on multiple cores. Here, we use Tokio's tasks to schedule some work to run
1113+
at the same time. We use a `JoinSet`, which is a list of tasks that lets us decide how
1114+
to best execute them.
1115+
1116+
One of the ways to execute tasks is `JoinSet::join_all`, which even gives us a neat
1117+
Vec that we can immediately return! You can also do this sequentially, with an iterator.
1118+
See if you can also find a way to do it that doesn't use `JoinSet`! You have access to
1119+
most of Tokio's task-based functionality here.
1120+
"""
1121+
10821122
# MACROS
10831123

10841124
[[exercises]]
10851125
name = "macros1"
1086-
dir = "21_macros"
1126+
dir = "22_macros"
10871127
test = false
10881128
hint = """
10891129
When you call a macro, you need to add something special compared to a regular
10901130
function call."""
10911131

10921132
[[exercises]]
10931133
name = "macros2"
1094-
dir = "21_macros"
1134+
dir = "22_macros"
10951135
test = false
10961136
hint = """
10971137
Macros don't quite play by the same rules as the rest of Rust, in terms of
@@ -1102,15 +1142,15 @@ Unlike other things in Rust, the order of "where you define a macro" versus
11021142

11031143
[[exercises]]
11041144
name = "macros3"
1105-
dir = "21_macros"
1145+
dir = "22_macros"
11061146
test = false
11071147
hint = """
11081148
In order to use a macro outside of its module, you need to do something
11091149
special to the module to lift the macro out into its parent."""
11101150

11111151
[[exercises]]
11121152
name = "macros4"
1113-
dir = "21_macros"
1153+
dir = "22_macros"
11141154
test = false
11151155
hint = """
11161156
You only need to add a single character to make this compile.
@@ -1127,7 +1167,7 @@ https://veykril.github.io/tlborm/"""
11271167

11281168
[[exercises]]
11291169
name = "clippy1"
1130-
dir = "22_clippy"
1170+
dir = "23_clippy"
11311171
test = false
11321172
strict_clippy = true
11331173
hint = """
@@ -1144,7 +1184,7 @@ appropriate replacement constant from `std::f32::consts`."""
11441184

11451185
[[exercises]]
11461186
name = "clippy2"
1147-
dir = "22_clippy"
1187+
dir = "23_clippy"
11481188
test = false
11491189
strict_clippy = true
11501190
hint = """
@@ -1157,7 +1197,7 @@ https://doc.rust-lang.org/std/option/#iterating-over-option"""
11571197

11581198
[[exercises]]
11591199
name = "clippy3"
1160-
dir = "22_clippy"
1200+
dir = "23_clippy"
11611201
test = false
11621202
strict_clippy = true
11631203
hint = "No hints this time!"
@@ -1166,20 +1206,20 @@ hint = "No hints this time!"
11661206

11671207
[[exercises]]
11681208
name = "using_as"
1169-
dir = "23_conversions"
1209+
dir = "24_conversions"
11701210
hint = """
11711211
Use the `as` operator to cast one of the operands in the last line of the
11721212
`average` function into the expected return type."""
11731213

11741214
[[exercises]]
11751215
name = "from_into"
1176-
dir = "23_conversions"
1216+
dir = "24_conversions"
11771217
hint = """
11781218
Follow the steps provided right before the `From` implementation."""
11791219

11801220
[[exercises]]
11811221
name = "from_str"
1182-
dir = "23_conversions"
1222+
dir = "24_conversions"
11831223
hint = """
11841224
The implementation of `FromStr` should return an `Ok` with a `Person` object,
11851225
or an `Err` with an error if the string is not valid.
@@ -1196,7 +1236,7 @@ https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reen
11961236

11971237
[[exercises]]
11981238
name = "try_from_into"
1199-
dir = "23_conversions"
1239+
dir = "24_conversions"
12001240
hint = """
12011241
Is there an implementation of `TryFrom` in the standard library that can both do
12021242
the required integer conversion and check the range of the input?
@@ -1206,6 +1246,6 @@ types?"""
12061246

12071247
[[exercises]]
12081248
name = "as_ref_mut"
1209-
dir = "23_conversions"
1249+
dir = "24_conversions"
12101250
hint = """
12111251
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""

solutions/21_async/async1.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Our loyal worker works hard to create a new number.
2+
#[derive(Default)]
3+
struct Worker;
4+
5+
struct NumberContainer {
6+
number: i32,
7+
}
8+
9+
impl Worker {
10+
async fn work(&self) -> NumberContainer {
11+
// Pretend this takes a while...
12+
let new_number = 32;
13+
NumberContainer { number: new_number }
14+
}
15+
}
16+
17+
impl NumberContainer {
18+
async fn extract_number(&self) -> i32 {
19+
// And this too...
20+
self.number
21+
}
22+
}
23+
24+
async fn run_worker() -> i32 {
25+
// TODO: Make our worker create a new number and return it.
26+
Worker.work().await.extract_number().await
27+
}
28+
29+
fn main() {
30+
// Feel free to experiment here. You may need to make some adjustments
31+
// to this function, though.
32+
}
33+
34+
mod tests {
35+
use super::*;
36+
37+
// Don't worry about this attribute for now.
38+
// If you want to know what this does, read the hint!
39+
#[tokio::test]
40+
// TODO: Fix the test function signature
41+
fn test_if_it_works() {
42+
let number = run_worker().await;
43+
assert_eq!(number, 32);
44+
}
45+
}

0 commit comments

Comments
 (0)