diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 4f725b70..262d2280 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -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" diff --git a/exercises/21_async/README.md b/exercises/21_async/README.md new file mode 100644 index 00000000..81bd018e --- /dev/null +++ b/exercises/21_async/README.md @@ -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 diff --git a/exercises/21_async/async1.rs b/exercises/21_async/async1.rs new file mode 100644 index 00000000..11f697ad --- /dev/null +++ b/exercises/21_async/async1.rs @@ -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); + } +} diff --git a/exercises/21_async/async2.rs b/exercises/21_async/async2.rs new file mode 100644 index 00000000..619fff83 --- /dev/null +++ b/exercises/21_async/async2.rs @@ -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 { + 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 { + 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]); + } +} diff --git a/exercises/21_macros/README.md b/exercises/22_macros/README.md similarity index 100% rename from exercises/21_macros/README.md rename to exercises/22_macros/README.md diff --git a/exercises/21_macros/macros1.rs b/exercises/22_macros/macros1.rs similarity index 100% rename from exercises/21_macros/macros1.rs rename to exercises/22_macros/macros1.rs diff --git a/exercises/21_macros/macros2.rs b/exercises/22_macros/macros2.rs similarity index 100% rename from exercises/21_macros/macros2.rs rename to exercises/22_macros/macros2.rs diff --git a/exercises/21_macros/macros3.rs b/exercises/22_macros/macros3.rs similarity index 100% rename from exercises/21_macros/macros3.rs rename to exercises/22_macros/macros3.rs diff --git a/exercises/21_macros/macros4.rs b/exercises/22_macros/macros4.rs similarity index 100% rename from exercises/21_macros/macros4.rs rename to exercises/22_macros/macros4.rs diff --git a/exercises/22_clippy/README.md b/exercises/23_clippy/README.md similarity index 100% rename from exercises/22_clippy/README.md rename to exercises/23_clippy/README.md diff --git a/exercises/22_clippy/clippy1.rs b/exercises/23_clippy/clippy1.rs similarity index 100% rename from exercises/22_clippy/clippy1.rs rename to exercises/23_clippy/clippy1.rs diff --git a/exercises/22_clippy/clippy2.rs b/exercises/23_clippy/clippy2.rs similarity index 100% rename from exercises/22_clippy/clippy2.rs rename to exercises/23_clippy/clippy2.rs diff --git a/exercises/22_clippy/clippy3.rs b/exercises/23_clippy/clippy3.rs similarity index 100% rename from exercises/22_clippy/clippy3.rs rename to exercises/23_clippy/clippy3.rs diff --git a/exercises/23_conversions/README.md b/exercises/24_conversions/README.md similarity index 100% rename from exercises/23_conversions/README.md rename to exercises/24_conversions/README.md diff --git a/exercises/23_conversions/as_ref_mut.rs b/exercises/24_conversions/as_ref_mut.rs similarity index 100% rename from exercises/23_conversions/as_ref_mut.rs rename to exercises/24_conversions/as_ref_mut.rs diff --git a/exercises/23_conversions/from_into.rs b/exercises/24_conversions/from_into.rs similarity index 100% rename from exercises/23_conversions/from_into.rs rename to exercises/24_conversions/from_into.rs diff --git a/exercises/23_conversions/from_str.rs b/exercises/24_conversions/from_str.rs similarity index 100% rename from exercises/23_conversions/from_str.rs rename to exercises/24_conversions/from_str.rs diff --git a/exercises/23_conversions/try_from_into.rs b/exercises/24_conversions/try_from_into.rs similarity index 100% rename from exercises/23_conversions/try_from_into.rs rename to exercises/24_conversions/try_from_into.rs diff --git a/exercises/23_conversions/using_as.rs b/exercises/24_conversions/using_as.rs similarity index 100% rename from exercises/23_conversions/using_as.rs rename to exercises/24_conversions/using_as.rs diff --git a/exercises/README.md b/exercises/README.md index 237f2f1e..564fe62b 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -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 | diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 516fd321..f24a2b0e 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -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` or `AsMut` as a trait bound to the functions.""" diff --git a/solutions/21_async/async1.rs b/solutions/21_async/async1.rs new file mode 100644 index 00000000..e5c7f5b4 --- /dev/null +++ b/solutions/21_async/async1.rs @@ -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); + } +} diff --git a/solutions/21_async/async2.rs b/solutions/21_async/async2.rs new file mode 100644 index 00000000..3535158a --- /dev/null +++ b/solutions/21_async/async2.rs @@ -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 { + 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 { + 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]); + } +} diff --git a/solutions/21_macros/macros1.rs b/solutions/22_macros/macros1.rs similarity index 100% rename from solutions/21_macros/macros1.rs rename to solutions/22_macros/macros1.rs diff --git a/solutions/21_macros/macros2.rs b/solutions/22_macros/macros2.rs similarity index 100% rename from solutions/21_macros/macros2.rs rename to solutions/22_macros/macros2.rs diff --git a/solutions/21_macros/macros3.rs b/solutions/22_macros/macros3.rs similarity index 100% rename from solutions/21_macros/macros3.rs rename to solutions/22_macros/macros3.rs diff --git a/solutions/21_macros/macros4.rs b/solutions/22_macros/macros4.rs similarity index 100% rename from solutions/21_macros/macros4.rs rename to solutions/22_macros/macros4.rs diff --git a/solutions/22_clippy/clippy1.rs b/solutions/23_clippy/clippy1.rs similarity index 100% rename from solutions/22_clippy/clippy1.rs rename to solutions/23_clippy/clippy1.rs diff --git a/solutions/22_clippy/clippy2.rs b/solutions/23_clippy/clippy2.rs similarity index 100% rename from solutions/22_clippy/clippy2.rs rename to solutions/23_clippy/clippy2.rs diff --git a/solutions/22_clippy/clippy3.rs b/solutions/23_clippy/clippy3.rs similarity index 100% rename from solutions/22_clippy/clippy3.rs rename to solutions/23_clippy/clippy3.rs diff --git a/solutions/23_conversions/as_ref_mut.rs b/solutions/24_conversions/as_ref_mut.rs similarity index 100% rename from solutions/23_conversions/as_ref_mut.rs rename to solutions/24_conversions/as_ref_mut.rs diff --git a/solutions/23_conversions/from_into.rs b/solutions/24_conversions/from_into.rs similarity index 100% rename from solutions/23_conversions/from_into.rs rename to solutions/24_conversions/from_into.rs diff --git a/solutions/23_conversions/from_str.rs b/solutions/24_conversions/from_str.rs similarity index 100% rename from solutions/23_conversions/from_str.rs rename to solutions/24_conversions/from_str.rs diff --git a/solutions/23_conversions/try_from_into.rs b/solutions/24_conversions/try_from_into.rs similarity index 100% rename from solutions/23_conversions/try_from_into.rs rename to solutions/24_conversions/try_from_into.rs diff --git a/solutions/23_conversions/using_as.rs b/solutions/24_conversions/using_as.rs similarity index 100% rename from solutions/23_conversions/using_as.rs rename to solutions/24_conversions/using_as.rs