mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-06-05 21:44:47 +00:00
feat: add async exercises
This commit is contained in:
parent
e73fff3bd4
commit
b2b3005670
@ -164,30 +164,34 @@ bin = [
|
||||
{ name = "threads2_sol", path = "../solutions/20_threads/threads2.rs" },
|
||||
{ name = "threads3", path = "../exercises/20_threads/threads3.rs" },
|
||||
{ name = "threads3_sol", path = "../solutions/20_threads/threads3.rs" },
|
||||
{ name = "macros1", path = "../exercises/21_macros/macros1.rs" },
|
||||
{ name = "macros1_sol", path = "../solutions/21_macros/macros1.rs" },
|
||||
{ name = "macros2", path = "../exercises/21_macros/macros2.rs" },
|
||||
{ name = "macros2_sol", path = "../solutions/21_macros/macros2.rs" },
|
||||
{ name = "macros3", path = "../exercises/21_macros/macros3.rs" },
|
||||
{ name = "macros3_sol", path = "../solutions/21_macros/macros3.rs" },
|
||||
{ name = "macros4", path = "../exercises/21_macros/macros4.rs" },
|
||||
{ name = "macros4_sol", path = "../solutions/21_macros/macros4.rs" },
|
||||
{ name = "clippy1", path = "../exercises/22_clippy/clippy1.rs" },
|
||||
{ name = "clippy1_sol", path = "../solutions/22_clippy/clippy1.rs" },
|
||||
{ name = "clippy2", path = "../exercises/22_clippy/clippy2.rs" },
|
||||
{ name = "clippy2_sol", path = "../solutions/22_clippy/clippy2.rs" },
|
||||
{ name = "clippy3", path = "../exercises/22_clippy/clippy3.rs" },
|
||||
{ name = "clippy3_sol", path = "../solutions/22_clippy/clippy3.rs" },
|
||||
{ name = "using_as", path = "../exercises/23_conversions/using_as.rs" },
|
||||
{ name = "using_as_sol", path = "../solutions/23_conversions/using_as.rs" },
|
||||
{ name = "from_into", path = "../exercises/23_conversions/from_into.rs" },
|
||||
{ name = "from_into_sol", path = "../solutions/23_conversions/from_into.rs" },
|
||||
{ name = "from_str", path = "../exercises/23_conversions/from_str.rs" },
|
||||
{ name = "from_str_sol", path = "../solutions/23_conversions/from_str.rs" },
|
||||
{ name = "try_from_into", path = "../exercises/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_sol", path = "../solutions/23_conversions/as_ref_mut.rs" },
|
||||
{ name = "async1", path = "../exercises/21_async/async1.rs" },
|
||||
{ name = "async1_sol", path = "../solutions/21_async/async1.rs" },
|
||||
{ name = "async2", path = "../exercises/21_async/async2.rs" },
|
||||
{ name = "async2_sol", path = "../solutions/21_async/async2.rs" },
|
||||
{ name = "macros1", path = "../exercises/22_macros/macros1.rs" },
|
||||
{ name = "macros1_sol", path = "../solutions/22_macros/macros1.rs" },
|
||||
{ name = "macros2", path = "../exercises/22_macros/macros2.rs" },
|
||||
{ name = "macros2_sol", path = "../solutions/22_macros/macros2.rs" },
|
||||
{ name = "macros3", path = "../exercises/22_macros/macros3.rs" },
|
||||
{ name = "macros3_sol", path = "../solutions/22_macros/macros3.rs" },
|
||||
{ name = "macros4", path = "../exercises/22_macros/macros4.rs" },
|
||||
{ name = "macros4_sol", path = "../solutions/22_macros/macros4.rs" },
|
||||
{ name = "clippy1", path = "../exercises/23_clippy/clippy1.rs" },
|
||||
{ name = "clippy1_sol", path = "../solutions/23_clippy/clippy1.rs" },
|
||||
{ name = "clippy2", path = "../exercises/23_clippy/clippy2.rs" },
|
||||
{ name = "clippy2_sol", path = "../solutions/23_clippy/clippy2.rs" },
|
||||
{ name = "clippy3", path = "../exercises/23_clippy/clippy3.rs" },
|
||||
{ name = "clippy3_sol", path = "../solutions/23_clippy/clippy3.rs" },
|
||||
{ name = "using_as", path = "../exercises/24_conversions/using_as.rs" },
|
||||
{ name = "using_as_sol", path = "../solutions/24_conversions/using_as.rs" },
|
||||
{ name = "from_into", path = "../exercises/24_conversions/from_into.rs" },
|
||||
{ name = "from_into_sol", path = "../solutions/24_conversions/from_into.rs" },
|
||||
{ name = "from_str", path = "../exercises/24_conversions/from_str.rs" },
|
||||
{ name = "from_str_sol", path = "../solutions/24_conversions/from_str.rs" },
|
||||
{ name = "try_from_into", path = "../exercises/24_conversions/try_from_into.rs" },
|
||||
{ name = "try_from_into_sol", path = "../solutions/24_conversions/try_from_into.rs" },
|
||||
{ name = "as_ref_mut", path = "../exercises/24_conversions/as_ref_mut.rs" },
|
||||
{ name = "as_ref_mut_sol", path = "../solutions/24_conversions/as_ref_mut.rs" },
|
||||
]
|
||||
|
||||
[package]
|
||||
@ -196,6 +200,9 @@ edition = "2024"
|
||||
# Don't publish the exercises on crates.io!
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.45.0", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
|
10
exercises/21_async/README.md
Normal file
10
exercises/21_async/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Async
|
||||
|
||||
Rust includes built-in support for asynchronous programming. In other languages, this might be known as Promises or
|
||||
Coroutines. async programming uses async functions, which are powerful, but may require some getting used to,
|
||||
especially if you haven't used something similar in another language.
|
||||
|
||||
The [relevant book chapter][1] is essential reading. The [tokio docs][2] are also very helpful!
|
||||
|
||||
[1]: https://doc.rust-lang.org/book/ch17-00-async-await.html
|
||||
[2]: https://tokio.rs/tokio/tutorial
|
44
exercises/21_async/async1.rs
Normal file
44
exercises/21_async/async1.rs
Normal file
@ -0,0 +1,44 @@
|
||||
// Our loyal worker works hard to create a new number.
|
||||
#[derive(Default)]
|
||||
struct Worker;
|
||||
|
||||
struct NumberContainer {
|
||||
number: i32,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
async fn work(&self) -> NumberContainer {
|
||||
// Pretend this takes a while...
|
||||
let new_number = 32;
|
||||
NumberContainer { number: new_number }
|
||||
}
|
||||
}
|
||||
|
||||
impl NumberContainer {
|
||||
async fn extract_number(&self) -> i32 {
|
||||
// And this too...
|
||||
self.number
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix the function signature!
|
||||
fn run_worker() -> i32 {
|
||||
// TODO: Make our worker create a new number and return it.
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Feel free to experiment here. You may need to make some adjustments
|
||||
// to this function, though.
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Don't worry about this attribute for now.
|
||||
// If you want to know what this does, read the hint!
|
||||
#[tokio::test]
|
||||
async fn test_if_it_works() {
|
||||
let number = run_worker().await;
|
||||
assert_eq!(number, 32);
|
||||
}
|
||||
}
|
42
exercises/21_async/async2.rs
Normal file
42
exercises/21_async/async2.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
// A MultiWorker can work with the power of 5 normal workers,
|
||||
// allowing us to create 5 new numbers at once!
|
||||
struct MultiWorker;
|
||||
|
||||
impl MultiWorker {
|
||||
async fn start_work(&self) -> JoinSet<i32> {
|
||||
let mut set = JoinSet::new();
|
||||
|
||||
for i in 30..35 {
|
||||
// TODO: `set.spawn` accepts an async function that will return the number
|
||||
// we want. Implement this function as a closure!
|
||||
set.spawn(???);
|
||||
}
|
||||
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_multi_worker() -> Vec<i32> {
|
||||
let tasks = MultiWorker.start_work().await;
|
||||
|
||||
// TODO: We have a bunch of tasks, how do we run them to completion
|
||||
// to get at the i32s they create?
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Feel free to experiment here. You may need to make some adjustments
|
||||
// to this function, though.
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_if_it_works() {
|
||||
let mut numbers = run_multi_worker().await;
|
||||
numbers.sort(); // in case tasks run out-of-order
|
||||
assert_eq!(numbers, vec![30, 31, 32, 33, 34]);
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
| iterators | §13.2-4 |
|
||||
| smart_pointers | §15, §16.3 |
|
||||
| threads | §16.1-3 |
|
||||
| async | §17 |
|
||||
| macros | §19.5 |
|
||||
| clippy | §21.4 |
|
||||
| conversions | n/a |
|
||||
|
@ -1079,11 +1079,51 @@ original sending end.
|
||||
Related section in The Book:
|
||||
https://doc.rust-lang.org/book/ch16-02-message-passing.html"""
|
||||
|
||||
# ASYNC
|
||||
|
||||
[[exercises]]
|
||||
name = "async1"
|
||||
dir = "21_async"
|
||||
test = true
|
||||
hint = """
|
||||
Async functions are not the same as normal functions -- they have to be marked with a
|
||||
special bit of syntax, `async fn`. These functions don't immediately return or even
|
||||
execute, you have to encourage them to do so by calling another special bit of syntax
|
||||
on them.
|
||||
|
||||
Another thing - an async function can't be properly called in a normal function. Think of
|
||||
it as something contagious -- everything that it touches needs to be marked as such. Keeping
|
||||
that in mind, what adjustment do you need to make to the function signature?
|
||||
|
||||
Aside:
|
||||
`#[tokio::test]` (and `#[tokio::main]`) are "magic" attributes that automatically
|
||||
set up what we call an async runtime. The Rust compiler intentionally doesn't supply
|
||||
a default implementation of this runtime. Tokio is by far the most popular
|
||||
community-developed runtime, and this macro does a lot of the heavy lifting to let
|
||||
us use it.
|
||||
"""
|
||||
|
||||
[[exercises]]
|
||||
name = "async2"
|
||||
dir = "21_async"
|
||||
test = true
|
||||
hint = """
|
||||
Async functions can be used to run multiple things in parallel, or to more efficiently
|
||||
run things on multiple cores. Here, we use Tokio's tasks to schedule some work to run
|
||||
at the same time. We use a `JoinSet`, which is a list of tasks that lets us decide how
|
||||
to best execute them.
|
||||
|
||||
One of the ways to execute tasks is `JoinSet::join_all`, which even gives us a neat
|
||||
Vec that we can immediately return! You can also do this sequentially, with an iterator.
|
||||
See if you can also find a way to do it that doesn't use `JoinSet`! You have access to
|
||||
most of Tokio's task-based functionality here.
|
||||
"""
|
||||
|
||||
# MACROS
|
||||
|
||||
[[exercises]]
|
||||
name = "macros1"
|
||||
dir = "21_macros"
|
||||
dir = "22_macros"
|
||||
test = false
|
||||
hint = """
|
||||
When you call a macro, you need to add something special compared to a regular
|
||||
@ -1091,7 +1131,7 @@ function call."""
|
||||
|
||||
[[exercises]]
|
||||
name = "macros2"
|
||||
dir = "21_macros"
|
||||
dir = "22_macros"
|
||||
test = false
|
||||
hint = """
|
||||
Macros don't quite play by the same rules as the rest of Rust, in terms of
|
||||
@ -1102,7 +1142,7 @@ Unlike other things in Rust, the order of "where you define a macro" versus
|
||||
|
||||
[[exercises]]
|
||||
name = "macros3"
|
||||
dir = "21_macros"
|
||||
dir = "22_macros"
|
||||
test = false
|
||||
hint = """
|
||||
In order to use a macro outside of its module, you need to do something
|
||||
@ -1110,7 +1150,7 @@ special to the module to lift the macro out into its parent."""
|
||||
|
||||
[[exercises]]
|
||||
name = "macros4"
|
||||
dir = "21_macros"
|
||||
dir = "22_macros"
|
||||
test = false
|
||||
hint = """
|
||||
You only need to add a single character to make this compile.
|
||||
@ -1127,7 +1167,7 @@ https://veykril.github.io/tlborm/"""
|
||||
|
||||
[[exercises]]
|
||||
name = "clippy1"
|
||||
dir = "22_clippy"
|
||||
dir = "23_clippy"
|
||||
test = false
|
||||
strict_clippy = true
|
||||
hint = """
|
||||
@ -1144,7 +1184,7 @@ appropriate replacement constant from `std::f32::consts`."""
|
||||
|
||||
[[exercises]]
|
||||
name = "clippy2"
|
||||
dir = "22_clippy"
|
||||
dir = "23_clippy"
|
||||
test = false
|
||||
strict_clippy = true
|
||||
hint = """
|
||||
@ -1157,7 +1197,7 @@ https://doc.rust-lang.org/std/option/#iterating-over-option"""
|
||||
|
||||
[[exercises]]
|
||||
name = "clippy3"
|
||||
dir = "22_clippy"
|
||||
dir = "23_clippy"
|
||||
test = false
|
||||
strict_clippy = true
|
||||
hint = "No hints this time!"
|
||||
@ -1166,20 +1206,20 @@ hint = "No hints this time!"
|
||||
|
||||
[[exercises]]
|
||||
name = "using_as"
|
||||
dir = "23_conversions"
|
||||
dir = "24_conversions"
|
||||
hint = """
|
||||
Use the `as` operator to cast one of the operands in the last line of the
|
||||
`average` function into the expected return type."""
|
||||
|
||||
[[exercises]]
|
||||
name = "from_into"
|
||||
dir = "23_conversions"
|
||||
dir = "24_conversions"
|
||||
hint = """
|
||||
Follow the steps provided right before the `From` implementation."""
|
||||
|
||||
[[exercises]]
|
||||
name = "from_str"
|
||||
dir = "23_conversions"
|
||||
dir = "24_conversions"
|
||||
hint = """
|
||||
The implementation of `FromStr` should return an `Ok` with a `Person` object,
|
||||
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
|
||||
|
||||
[[exercises]]
|
||||
name = "try_from_into"
|
||||
dir = "23_conversions"
|
||||
dir = "24_conversions"
|
||||
hint = """
|
||||
Is there an implementation of `TryFrom` in the standard library that can both do
|
||||
the required integer conversion and check the range of the input?
|
||||
@ -1206,6 +1246,6 @@ types?"""
|
||||
|
||||
[[exercises]]
|
||||
name = "as_ref_mut"
|
||||
dir = "23_conversions"
|
||||
dir = "24_conversions"
|
||||
hint = """
|
||||
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""
|
||||
|
45
solutions/21_async/async1.rs
Normal file
45
solutions/21_async/async1.rs
Normal file
@ -0,0 +1,45 @@
|
||||
// Our loyal worker works hard to create a new number.
|
||||
#[derive(Default)]
|
||||
struct Worker;
|
||||
|
||||
struct NumberContainer {
|
||||
number: i32,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
async fn work(&self) -> NumberContainer {
|
||||
// Pretend this takes a while...
|
||||
let new_number = 32;
|
||||
NumberContainer { number: new_number }
|
||||
}
|
||||
}
|
||||
|
||||
impl NumberContainer {
|
||||
async fn extract_number(&self) -> i32 {
|
||||
// And this too...
|
||||
self.number
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_worker() -> i32 {
|
||||
// TODO: Make our worker create a new number and return it.
|
||||
Worker.work().await.extract_number().await
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Feel free to experiment here. You may need to make some adjustments
|
||||
// to this function, though.
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Don't worry about this attribute for now.
|
||||
// If you want to know what this does, read the hint!
|
||||
#[tokio::test]
|
||||
// TODO: Fix the test function signature
|
||||
fn test_if_it_works() {
|
||||
let number = run_worker().await;
|
||||
assert_eq!(number, 32);
|
||||
}
|
||||
}
|
43
solutions/21_async/async2.rs
Normal file
43
solutions/21_async/async2.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
// A MultiWorker can work with the power of 5 normal workers,
|
||||
// allowing us to create 5 new numbers at once!
|
||||
struct MultiWorker;
|
||||
|
||||
impl MultiWorker {
|
||||
async fn start_work(&self) -> JoinSet<i32> {
|
||||
let mut set = JoinSet::new();
|
||||
|
||||
for i in 30..35 {
|
||||
// TODO: `set.spawn` accepts an async function that will return the number
|
||||
// we want. Implement this function as a closure!
|
||||
set.spawn(async move { i });
|
||||
}
|
||||
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_multi_worker() -> Vec<i32> {
|
||||
let tasks = MultiWorker.start_work().await;
|
||||
|
||||
// TODO: We have a bunch of tasks, how do we run them to completion
|
||||
// to get at the i32s they create?
|
||||
tasks.join_all().await
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Feel free to experiment here. You may need to make some adjustments
|
||||
// to this function, though.
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_if_it_works() {
|
||||
let mut numbers = run_multi_worker().await;
|
||||
numbers.sort(); // in case tasks run out-of-order
|
||||
assert_eq!(numbers, vec![30, 31, 32, 33, 34]);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user