From 98ef272777a20f75775ec6ae1723a86eff18e7b6 Mon Sep 17 00:00:00 2001 From: BraCR10 Date: Thu, 10 Jul 2025 01:40:15 -0600 Subject: [PATCH 1/6] My progress 15th challenge --- exercises/00_intro/intro2.rs | 2 +- exercises/01_variables/README.md | 2 +- exercises/01_variables/variables1.rs | 2 +- exercises/01_variables/variables2.rs | 2 +- exercises/01_variables/variables3.rs | 2 +- exercises/01_variables/variables4.rs | 2 +- exercises/01_variables/variables5.rs | 4 +-- exercises/01_variables/variables6.rs | 2 +- exercises/02_functions/functions1.rs | 1 + exercises/02_functions/functions2.rs | 2 +- exercises/02_functions/functions3.rs | 2 +- exercises/02_functions/functions4.rs | 2 +- exercises/02_functions/functions5.rs | 2 +- exercises/03_if/if1.rs | 6 ++++ exercises/03_if/if2.rs | 8 +++-- exercises/03_if/if3.rs | 4 +-- .../04_primitive_types/primitive_types1.rs | 2 ++ .../04_primitive_types/primitive_types2.rs | 2 +- .../04_primitive_types/primitive_types3.rs | 2 +- .../04_primitive_types/primitive_types4.rs | 2 +- .../04_primitive_types/primitive_types5.rs | 2 +- .../04_primitive_types/primitive_types6.rs | 4 ++- exercises/05_vecs/vecs1.rs | 2 +- exercises/05_vecs/vecs2.rs | 5 +-- .../06_move_semantics/move_semantics1.rs | 2 +- .../06_move_semantics/move_semantics2.rs | 2 +- .../06_move_semantics/move_semantics3.rs | 2 +- .../06_move_semantics/move_semantics4.rs | 2 +- .../06_move_semantics/move_semantics5.rs | 9 +++--- exercises/07_structs/structs1.rs | 15 ++++++--- exercises/07_structs/structs2.rs | 17 +++++++++- exercises/07_structs/structs3.rs | 9 ++++-- exercises/08_enums/README.md | 4 +-- exercises/08_enums/enums1.rs | 5 +++ exercises/08_enums/enums2.rs | 6 +++- exercises/08_enums/enums3.rs | 7 ++++ exercises/09_strings/strings1.rs | 2 +- exercises/09_strings/strings2.rs | 2 +- exercises/09_strings/strings3.rs | 4 ++- exercises/09_strings/strings4.rs | 20 ++++++------ exercises/10_modules/modules1.rs | 6 ++-- exercises/10_modules/modules2.rs | 6 ++-- exercises/10_modules/modules3.rs | 2 ++ exercises/11_hashmaps/hashmaps1.rs | 14 ++++++-- exercises/11_hashmaps/hashmaps2.rs | 5 +++ exercises/11_hashmaps/hashmaps3.rs | 25 +++++++++++++-- exercises/12_options/options1.rs | 32 +++++++++++-------- exercises/12_options/options2.rs | 4 +-- exercises/12_options/options3.rs | 4 +-- exercises/13_error_handling/README.md | 6 ++-- exercises/13_error_handling/errors1.rs | 6 ++-- exercises/13_error_handling/errors2.rs | 3 +- exercises/13_error_handling/errors3.rs | 5 ++- exercises/13_error_handling/errors4.rs | 8 ++++- exercises/13_error_handling/errors5.rs | 4 +-- exercises/13_error_handling/errors6.rs | 6 ++-- exercises/14_generics/generics1.rs | 2 +- exercises/14_generics/generics2.rs | 8 ++--- exercises/21_macros/README.md | 3 +- exercises/22_clippy/clippy3.rs | 6 ++-- exercises/quizzes/quiz1.rs | 7 ++++ exercises/quizzes/quiz2.rs | 14 ++++++-- 62 files changed, 238 insertions(+), 112 deletions(-) diff --git a/exercises/00_intro/intro2.rs b/exercises/00_intro/intro2.rs index c6cb6451ae..03e376ed35 100644 --- a/exercises/00_intro/intro2.rs +++ b/exercises/00_intro/intro2.rs @@ -1,4 +1,4 @@ fn main() { // TODO: Fix the code to print "Hello world!". - printline!("Hello world!"); + println!("Hello world!"); } diff --git a/exercises/01_variables/README.md b/exercises/01_variables/README.md index 5ba2efcaa2..7964ff29f4 100644 --- a/exercises/01_variables/README.md +++ b/exercises/01_variables/README.md @@ -1,7 +1,7 @@ # Variables In Rust, variables are immutable by default. -When a variable is immutable, once a value is bound to a name, you can't change that value. +When a variable is immutable, once a value is bound to a name, you can’t change that value. You can make them mutable by adding `mut` in front of the variable name. ## Further information diff --git a/exercises/01_variables/variables1.rs b/exercises/01_variables/variables1.rs index f83b44d415..ec1bcacb53 100644 --- a/exercises/01_variables/variables1.rs +++ b/exercises/01_variables/variables1.rs @@ -1,6 +1,6 @@ fn main() { // TODO: Add the missing keyword. - x = 5; + let x = 5; println!("x has the value {x}"); } diff --git a/exercises/01_variables/variables2.rs b/exercises/01_variables/variables2.rs index e2a360351a..2ee9d7e23f 100644 --- a/exercises/01_variables/variables2.rs +++ b/exercises/01_variables/variables2.rs @@ -1,6 +1,6 @@ fn main() { // TODO: Change the line below to fix the compiler error. - let x; + let x = 5; if x == 10 { println!("x is ten!"); diff --git a/exercises/01_variables/variables3.rs b/exercises/01_variables/variables3.rs index 06f35bb1e2..8724e03cc1 100644 --- a/exercises/01_variables/variables3.rs +++ b/exercises/01_variables/variables3.rs @@ -1,6 +1,6 @@ fn main() { // TODO: Change the line below to fix the compiler error. - let x: i32; + let x: i32=5; println!("Number {x}"); } diff --git a/exercises/01_variables/variables4.rs b/exercises/01_variables/variables4.rs index 6c138b18b9..9c49fad82d 100644 --- a/exercises/01_variables/variables4.rs +++ b/exercises/01_variables/variables4.rs @@ -1,6 +1,6 @@ // TODO: Fix the compiler error. fn main() { - let x = 3; + let mut x = 3; println!("Number {x}"); x = 5; // Don't change this line diff --git a/exercises/01_variables/variables5.rs b/exercises/01_variables/variables5.rs index cf5620da5f..3ceae2dd57 100644 --- a/exercises/01_variables/variables5.rs +++ b/exercises/01_variables/variables5.rs @@ -1,8 +1,8 @@ fn main() { let number = "T-H-R-E-E"; // Don't change this line - println!("Spell a number: {number}"); + println!("Spell a number: {number}" ); // TODO: Fix the compiler error by changing the line below without renaming the variable. - number = 3; + let number = 5; println!("Number plus two is: {}", number + 2); } diff --git a/exercises/01_variables/variables6.rs b/exercises/01_variables/variables6.rs index 4a040fddac..6b50bbffeb 100644 --- a/exercises/01_variables/variables6.rs +++ b/exercises/01_variables/variables6.rs @@ -1,5 +1,5 @@ // TODO: Change the line below to fix the compiler error. -const NUMBER = 3; +const NUMBER:u8 = 3; fn main() { println!("Number: {NUMBER}"); diff --git a/exercises/02_functions/functions1.rs b/exercises/02_functions/functions1.rs index a812c21bf2..a87707a79d 100644 --- a/exercises/02_functions/functions1.rs +++ b/exercises/02_functions/functions1.rs @@ -1,5 +1,6 @@ // TODO: Add some function with the name `call_me` without arguments or a return value. +fn call_me(){} fn main() { call_me(); // Don't change this line } diff --git a/exercises/02_functions/functions2.rs b/exercises/02_functions/functions2.rs index 2c773c6b7f..fe36fee814 100644 --- a/exercises/02_functions/functions2.rs +++ b/exercises/02_functions/functions2.rs @@ -1,5 +1,5 @@ // TODO: Add the missing type of the argument `num` after the colon `:`. -fn call_me(num:) { +fn call_me(num:i32) { for i in 0..num { println!("Ring! Call number {}", i + 1); } diff --git a/exercises/02_functions/functions3.rs b/exercises/02_functions/functions3.rs index 8d65477219..b234db2ac0 100644 --- a/exercises/02_functions/functions3.rs +++ b/exercises/02_functions/functions3.rs @@ -6,5 +6,5 @@ fn call_me(num: u8) { fn main() { // TODO: Fix the function call. - call_me(); + call_me(8); } diff --git a/exercises/02_functions/functions4.rs b/exercises/02_functions/functions4.rs index b22bffdaf2..60ab17d4f8 100644 --- a/exercises/02_functions/functions4.rs +++ b/exercises/02_functions/functions4.rs @@ -8,7 +8,7 @@ fn is_even(num: i64) -> bool { } // TODO: Fix the function signature. -fn sale_price(price: i64) -> { +fn sale_price(price: i64) -> i64{ if is_even(price) { price - 10 } else { diff --git a/exercises/02_functions/functions5.rs b/exercises/02_functions/functions5.rs index 34a2ac7dce..c92d17cba0 100644 --- a/exercises/02_functions/functions5.rs +++ b/exercises/02_functions/functions5.rs @@ -1,6 +1,6 @@ // TODO: Fix the function body without changing the signature. fn square(num: i32) -> i32 { - num * num; + num * num } fn main() { diff --git a/exercises/03_if/if1.rs b/exercises/03_if/if1.rs index e5a3c5a5b2..babe32d473 100644 --- a/exercises/03_if/if1.rs +++ b/exercises/03_if/if1.rs @@ -4,6 +4,12 @@ fn bigger(a: i32, b: i32) -> i32 { // Do not use: // - another function call // - additional variables + if a > b { + return a; + } else if a < b { + return b; + } + a } fn main() { diff --git a/exercises/03_if/if2.rs b/exercises/03_if/if2.rs index ca8493cc81..a6bcce5324 100644 --- a/exercises/03_if/if2.rs +++ b/exercises/03_if/if2.rs @@ -2,8 +2,10 @@ fn picky_eater(food: &str) -> &str { if food == "strawberry" { "Yummy!" - } else { - 1 + } else if food=="potato"{ + "I guess I can eat that." + }else { + "No thanks!" } } @@ -19,7 +21,7 @@ mod tests { #[test] fn yummy_food() { - // This means that calling `picky_eater` with the argument "strawberry" should return "Yummy!". + // This means that calling `picky_eater` with the argument "food" should return "Yummy!". assert_eq!(picky_eater("strawberry"), "Yummy!"); } diff --git a/exercises/03_if/if3.rs b/exercises/03_if/if3.rs index 89164eb218..6d7a7f40d3 100644 --- a/exercises/03_if/if3.rs +++ b/exercises/03_if/if3.rs @@ -3,11 +3,11 @@ fn animal_habitat(animal: &str) -> &str { let identifier = if animal == "crab" { 1 } else if animal == "gopher" { - 2.0 + 2 } else if animal == "snake" { 3 } else { - "Unknown" + 4 }; // Don't change the expression below! diff --git a/exercises/04_primitive_types/primitive_types1.rs b/exercises/04_primitive_types/primitive_types1.rs index 84923c7500..8ee86e1d85 100644 --- a/exercises/04_primitive_types/primitive_types1.rs +++ b/exercises/04_primitive_types/primitive_types1.rs @@ -9,6 +9,8 @@ fn main() { // TODO: Define a boolean variable with the name `is_evening` before the `if` statement below. // The value of the variable should be the negation (opposite) of `is_morning`. // let … + + let is_evening=!is_morning; if is_evening { println!("Good evening!"); } diff --git a/exercises/04_primitive_types/primitive_types2.rs b/exercises/04_primitive_types/primitive_types2.rs index 14018475dd..ab07fecf1f 100644 --- a/exercises/04_primitive_types/primitive_types2.rs +++ b/exercises/04_primitive_types/primitive_types2.rs @@ -17,7 +17,7 @@ fn main() { // Try a letter, try a digit (in single quotes), try a special character, try a character // from a different language than your own, try an emoji 😉 // let your_character = ''; - + let your_character = '*'; if your_character.is_alphabetic() { println!("Alphabetical!"); } else if your_character.is_numeric() { diff --git a/exercises/04_primitive_types/primitive_types3.rs b/exercises/04_primitive_types/primitive_types3.rs index 9b79c0cf2a..4bf62a7f9f 100644 --- a/exercises/04_primitive_types/primitive_types3.rs +++ b/exercises/04_primitive_types/primitive_types3.rs @@ -1,7 +1,7 @@ fn main() { // TODO: Create an array called `a` with at least 100 elements in it. // let a = ??? - + let a=[1;101]; if a.len() >= 100 { println!("Wow, that's a big array!"); } else { diff --git a/exercises/04_primitive_types/primitive_types4.rs b/exercises/04_primitive_types/primitive_types4.rs index 16e4fd9321..a15feb1ca1 100644 --- a/exercises/04_primitive_types/primitive_types4.rs +++ b/exercises/04_primitive_types/primitive_types4.rs @@ -9,7 +9,7 @@ mod tests { let a = [1, 2, 3, 4, 5]; // TODO: Get a slice called `nice_slice` out of the array `a` so that the test passes. - // let nice_slice = ??? + let nice_slice = &a[1..4]; assert_eq!([2, 3, 4], nice_slice); } diff --git a/exercises/04_primitive_types/primitive_types5.rs b/exercises/04_primitive_types/primitive_types5.rs index 6e00ef51f8..a49175c19a 100644 --- a/exercises/04_primitive_types/primitive_types5.rs +++ b/exercises/04_primitive_types/primitive_types5.rs @@ -3,6 +3,6 @@ fn main() { // TODO: Destructure the `cat` tuple in one statement so that the println works. // let /* your pattern here */ = cat; - + let (name,age)=cat; println!("{name} is {age} years old"); } diff --git a/exercises/04_primitive_types/primitive_types6.rs b/exercises/04_primitive_types/primitive_types6.rs index a97e53110e..b886d8294f 100644 --- a/exercises/04_primitive_types/primitive_types6.rs +++ b/exercises/04_primitive_types/primitive_types6.rs @@ -1,5 +1,6 @@ fn main() { // You can optionally experiment here. + } #[cfg(test)] @@ -11,7 +12,8 @@ mod tests { // TODO: Use a tuple index to access the second element of `numbers` // and assign it to a variable called `second`. // let second = ???; - + let second = numbers.1; assert_eq!(second, 2, "This is not the 2nd number in the tuple!"); + } } diff --git a/exercises/05_vecs/vecs1.rs b/exercises/05_vecs/vecs1.rs index 68e1affaa7..56bc77e44c 100644 --- a/exercises/05_vecs/vecs1.rs +++ b/exercises/05_vecs/vecs1.rs @@ -3,7 +3,7 @@ fn array_and_vec() -> ([i32; 4], Vec) { // TODO: Create a vector called `v` which contains the exact same elements as in the array `a`. // Use the vector macro. - // let v = ???; + let v: Vec = a.into_iter().collect(); (a, v) } diff --git a/exercises/05_vecs/vecs2.rs b/exercises/05_vecs/vecs2.rs index a9be2580f2..bcd45ea19f 100644 --- a/exercises/05_vecs/vecs2.rs +++ b/exercises/05_vecs/vecs2.rs @@ -1,9 +1,10 @@ fn vec_loop(input: &[i32]) -> Vec { - let mut output = Vec::new(); + let mut output: Vec = Vec::new(); for element in input { // TODO: Multiply each element in the `input` slice by 2 and push it to // the `output` vector. + output.push(element * 2); } output @@ -24,7 +25,7 @@ fn vec_map(input: &[i32]) -> Vec { input .iter() .map(|element| { - // ??? + element*2 }) .collect() } diff --git a/exercises/06_move_semantics/move_semantics1.rs b/exercises/06_move_semantics/move_semantics1.rs index 4eb3d618ef..bf55943249 100644 --- a/exercises/06_move_semantics/move_semantics1.rs +++ b/exercises/06_move_semantics/move_semantics1.rs @@ -1,6 +1,6 @@ // TODO: Fix the compiler error in this function. fn fill_vec(vec: Vec) -> Vec { - let vec = vec; + let mut vec = vec; vec.push(88); diff --git a/exercises/06_move_semantics/move_semantics2.rs b/exercises/06_move_semantics/move_semantics2.rs index a3ab7a0f18..e205b81357 100644 --- a/exercises/06_move_semantics/move_semantics2.rs +++ b/exercises/06_move_semantics/move_semantics2.rs @@ -20,7 +20,7 @@ mod tests { fn move_semantics2() { let vec0 = vec![22, 44, 66]; - let vec1 = fill_vec(vec0); + let vec1 = fill_vec(vec0.clone()); assert_eq!(vec0, [22, 44, 66]); assert_eq!(vec1, [22, 44, 66, 88]); diff --git a/exercises/06_move_semantics/move_semantics3.rs b/exercises/06_move_semantics/move_semantics3.rs index 11dbbbebff..4a90c2117c 100644 --- a/exercises/06_move_semantics/move_semantics3.rs +++ b/exercises/06_move_semantics/move_semantics3.rs @@ -1,5 +1,5 @@ // TODO: Fix the compiler error in the function without adding any new line. -fn fill_vec(vec: Vec) -> Vec { +fn fill_vec(mut vec: Vec) -> Vec { vec.push(88); vec diff --git a/exercises/06_move_semantics/move_semantics4.rs b/exercises/06_move_semantics/move_semantics4.rs index 56da988cd4..89b412f093 100644 --- a/exercises/06_move_semantics/move_semantics4.rs +++ b/exercises/06_move_semantics/move_semantics4.rs @@ -10,8 +10,8 @@ mod tests { fn move_semantics4() { let mut x = Vec::new(); let y = &mut x; - let z = &mut x; y.push(42); + let z = &mut x; z.push(13); assert_eq!(x, [42, 13]); } diff --git a/exercises/06_move_semantics/move_semantics5.rs b/exercises/06_move_semantics/move_semantics5.rs index cd0dafd08d..efa06f7afe 100644 --- a/exercises/06_move_semantics/move_semantics5.rs +++ b/exercises/06_move_semantics/move_semantics5.rs @@ -4,12 +4,12 @@ // removing references (the character `&`). // Shouldn't take ownership -fn get_char(data: String) -> char { +fn get_char(data: &String) -> char { data.chars().last().unwrap() } // Should take ownership -fn string_uppercase(mut data: &String) { +fn string_uppercase(mut data: String) { data = data.to_uppercase(); println!("{data}"); @@ -18,7 +18,8 @@ fn string_uppercase(mut data: &String) { fn main() { let data = "Rust is great!".to_string(); - get_char(data); + get_char(&data); + + string_uppercase(data); - string_uppercase(&data); } diff --git a/exercises/07_structs/structs1.rs b/exercises/07_structs/structs1.rs index 959c4c6a68..534b6242bb 100644 --- a/exercises/07_structs/structs1.rs +++ b/exercises/07_structs/structs1.rs @@ -1,9 +1,16 @@ struct ColorRegularStruct { // TODO: Add the fields that the test `regular_structs` expects. // What types should the fields have? What are the minimum and maximum values for RGB colors? + red : u8, + green :u8, + blue:u8 } -struct ColorTupleStruct(/* TODO: Add the fields that the test `tuple_structs` expects */); +struct ColorTupleStruct( + u8, + u8, + u8 +); #[derive(Debug)] struct UnitStruct; @@ -19,7 +26,7 @@ mod tests { #[test] fn regular_structs() { // TODO: Instantiate a regular struct. - // let green = + let green = ColorRegularStruct { red: 0, green: 255, blue: 0 }; assert_eq!(green.red, 0); assert_eq!(green.green, 255); @@ -29,7 +36,7 @@ mod tests { #[test] fn tuple_structs() { // TODO: Instantiate a tuple struct. - // let green = + let green = ColorTupleStruct(0,255,0); assert_eq!(green.0, 0); assert_eq!(green.1, 255); @@ -39,7 +46,7 @@ mod tests { #[test] fn unit_structs() { // TODO: Instantiate a unit struct. - // let unit_struct = + let unit_struct = UnitStruct; let message = format!("{unit_struct:?}s are fun!"); assert_eq!(message, "UnitStructs are fun!"); diff --git a/exercises/07_structs/structs2.rs b/exercises/07_structs/structs2.rs index 79141af95d..167089ceef 100644 --- a/exercises/07_structs/structs2.rs +++ b/exercises/07_structs/structs2.rs @@ -21,6 +21,20 @@ fn create_order_template() -> Order { } } +impl Order { + fn create_orders(name:String,year:u32,made_by_phone:bool,made_by_mobile:bool,made_by_email:bool,item_number:u32,count:u32) -> Order { + Order { + name, + year, + made_by_phone, + made_by_mobile, + made_by_email, + item_number, + count, + } + } +} + fn main() { // You can optionally experiment here. } @@ -34,7 +48,8 @@ mod tests { let order_template = create_order_template(); // TODO: Create your own order using the update syntax and template above! - // let your_order = + let your_order = Order::create_orders("Hacker in Rust".to_string(), order_template.year, order_template.made_by_phone, + order_template.made_by_mobile, order_template.made_by_email, order_template.item_number, 1); assert_eq!(your_order.name, "Hacker in Rust"); assert_eq!(your_order.year, order_template.year); diff --git a/exercises/07_structs/structs3.rs b/exercises/07_structs/structs3.rs index 69e5ced7f8..9ccaa7428b 100644 --- a/exercises/07_structs/structs3.rs +++ b/exercises/07_structs/structs3.rs @@ -15,7 +15,7 @@ impl Package { // learn about error handling later. panic!("Can't ship a package with weight below 10 grams"); } - + Self { sender_country, recipient_country, @@ -24,14 +24,17 @@ impl Package { } // TODO: Add the correct return type to the function signature. - fn is_international(&self) { + fn is_international(&self)->bool { // TODO: Read the tests that use this method to find out when a package // is considered international. + self.recipient_country != self.sender_country } // TODO: Add the correct return type to the function signature. - fn get_fees(&self, cents_per_gram: u32) { + fn get_fees(&self, cents_per_gram: u32)->u32 { // TODO: Calculate the package's fees. + + self.weight_in_grams*cents_per_gram } } diff --git a/exercises/08_enums/README.md b/exercises/08_enums/README.md index b05cb42260..30d4d91db2 100644 --- a/exercises/08_enums/README.md +++ b/exercises/08_enums/README.md @@ -1,10 +1,10 @@ # Enums Rust allows you to define types called "enums" which enumerate possible values. -Enums are a feature in many languages, but their capabilities differ in each language. Rust's enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell. +Enums are a feature in many languages, but their capabilities differ in each language. Rust’s enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell. Useful in combination with enums is Rust's "pattern matching" facility, which makes it easy to run different code for different values of an enumeration. ## Further information - [Enums](https://doc.rust-lang.org/book/ch06-00-enums.html) -- [Pattern syntax](https://doc.rust-lang.org/book/ch19-03-pattern-syntax.html) +- [Pattern syntax](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html) diff --git a/exercises/08_enums/enums1.rs b/exercises/08_enums/enums1.rs index c0d0c308d2..635a334919 100644 --- a/exercises/08_enums/enums1.rs +++ b/exercises/08_enums/enums1.rs @@ -1,6 +1,11 @@ #[derive(Debug)] enum Message { // TODO: Define a few types of messages as used below. + Resize + , Move + , Echo + , ChangeColor + , Quit } fn main() { diff --git a/exercises/08_enums/enums2.rs b/exercises/08_enums/enums2.rs index d70f6398d1..07aee262f7 100644 --- a/exercises/08_enums/enums2.rs +++ b/exercises/08_enums/enums2.rs @@ -6,7 +6,11 @@ struct Point { #[derive(Debug)] enum Message { - // TODO: Define the different variants used below. + Resize { width: u64, height: u64 }, + Move(Point), + Echo(String), + ChangeColor(u8, u8, u8), + Quit, } impl Message { diff --git a/exercises/08_enums/enums3.rs b/exercises/08_enums/enums3.rs index cb05f657c2..9aa2507688 100644 --- a/exercises/08_enums/enums3.rs +++ b/exercises/08_enums/enums3.rs @@ -46,6 +46,13 @@ impl State { fn process(&mut self, message: Message) { // TODO: Create a match expression to process the different message // variants using the methods defined above. + match message { + Message::Resize { width, height } => State::resize(self, width, height), + Message::Move(point) => State::move_position(self, point), + Message::Echo(s) => State::echo(self, s), + Message::ChangeColor(r, g, b) => State::change_color(self, r, g, b), + Message::Quit => State::quit(self), + } } } diff --git a/exercises/09_strings/strings1.rs b/exercises/09_strings/strings1.rs index 6abdbb48f0..06755aae40 100644 --- a/exercises/09_strings/strings1.rs +++ b/exercises/09_strings/strings1.rs @@ -1,6 +1,6 @@ // TODO: Fix the compiler error without changing the function signature. fn current_favorite_color() -> String { - "blue" + "blue".to_string() } fn main() { diff --git a/exercises/09_strings/strings2.rs b/exercises/09_strings/strings2.rs index 93d9cb6b7f..9e605a6888 100644 --- a/exercises/09_strings/strings2.rs +++ b/exercises/09_strings/strings2.rs @@ -6,7 +6,7 @@ fn is_a_color_word(attempt: &str) -> bool { fn main() { let word = String::from("green"); // Don't change this line. - if is_a_color_word(word) { + if is_a_color_word(word.as_str()) { println!("That is a color word I know!"); } else { println!("That is not a color word I know."); diff --git a/exercises/09_strings/strings3.rs b/exercises/09_strings/strings3.rs index f5e45b0ffc..0dec1ca7b2 100644 --- a/exercises/09_strings/strings3.rs +++ b/exercises/09_strings/strings3.rs @@ -1,13 +1,16 @@ fn trim_me(input: &str) -> &str { // TODO: Remove whitespace from both ends of a string. + input.trim() } fn compose_me(input: &str) -> String { // TODO: Add " world!" to the string! There are multiple ways to do this. + input.to_string() + " world!" } fn replace_me(input: &str) -> String { // TODO: Replace "cars" in the string with "balloons". + input.replace("cars", "balloons") } fn main() { @@ -23,7 +26,6 @@ mod tests { assert_eq!(trim_me("Hello! "), "Hello!"); assert_eq!(trim_me(" What's up!"), "What's up!"); assert_eq!(trim_me(" Hola! "), "Hola!"); - assert_eq!(trim_me("Hi!"), "Hi!"); } #[test] diff --git a/exercises/09_strings/strings4.rs b/exercises/09_strings/strings4.rs index 473072636d..9d5dbce28c 100644 --- a/exercises/09_strings/strings4.rs +++ b/exercises/09_strings/strings4.rs @@ -13,25 +13,25 @@ fn string(arg: String) { // Your task is to replace `placeholder(…)` with either `string_slice(…)` // or `string(…)` depending on what you think each value is. fn main() { - placeholder("blue"); + string_slice("blue"); - placeholder("red".to_string()); + string("red".to_string()); - placeholder(String::from("hi")); + string(String::from("hi")); - placeholder("rust is fun!".to_owned()); + string("rust is fun!".to_owned()); - placeholder("nice weather".into()); + string_slice("nice weather"); - placeholder(format!("Interpolation {}", "Station")); + string(format!("Interpolation {}", "Station")); // WARNING: This is byte indexing, not character indexing. // Character indexing can be done using `s.chars().nth(INDEX)`. - placeholder(&String::from("abc")[0..1]); + string_slice(&String::from("abc")[0..1]); - placeholder(" hello there ".trim()); + string_slice(" hello there ".trim()); - placeholder("Happy Monday!".replace("Mon", "Tues")); + string("Happy Monday!".replace("Mon", "Tues")); - placeholder("mY sHiFt KeY iS sTiCkY".to_lowercase()); + string("mY sHiFt KeY iS sTiCkY".to_lowercase()); } diff --git a/exercises/10_modules/modules1.rs b/exercises/10_modules/modules1.rs index d97ab23a53..89668df148 100644 --- a/exercises/10_modules/modules1.rs +++ b/exercises/10_modules/modules1.rs @@ -1,12 +1,12 @@ // TODO: Fix the compiler error about calling a private function. mod sausage_factory { // Don't let anybody outside of this module see this! - fn get_secret_recipe() -> String { + fn _get_secret_recipe() -> String { String::from("Ginger") } - fn make_sausage() { - get_secret_recipe(); + pub fn make_sausage() { + _get_secret_recipe(); println!("sausage!"); } } diff --git a/exercises/10_modules/modules2.rs b/exercises/10_modules/modules2.rs index 782a70eace..14c037519c 100644 --- a/exercises/10_modules/modules2.rs +++ b/exercises/10_modules/modules2.rs @@ -3,8 +3,8 @@ mod delicious_snacks { // TODO: Add the following two `use` statements after fixing them. - // use self::fruits::PEAR as ???; - // use self::veggies::CUCUMBER as ???; + pub use self::fruits::PEAR as fruit; + pub use self::veggies::CUCUMBER as veggie; mod fruits { pub const PEAR: &str = "Pear"; @@ -18,6 +18,8 @@ mod delicious_snacks { } fn main() { + + use delicious_snacks; println!( "favorite snacks: {} and {}", delicious_snacks::fruit, diff --git a/exercises/10_modules/modules3.rs b/exercises/10_modules/modules3.rs index 691608d275..7ecb0048cb 100644 --- a/exercises/10_modules/modules3.rs +++ b/exercises/10_modules/modules3.rs @@ -5,7 +5,9 @@ // your scope. Bonus style points if you can do it with one line! // use ???; +use std::time::{SystemTime, UNIX_EPOCH}; fn main() { + match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()), Err(_) => panic!("SystemTime before UNIX EPOCH!"), diff --git a/exercises/11_hashmaps/hashmaps1.rs b/exercises/11_hashmaps/hashmaps1.rs index 74001d0432..aa8c60ed1e 100644 --- a/exercises/11_hashmaps/hashmaps1.rs +++ b/exercises/11_hashmaps/hashmaps1.rs @@ -8,18 +8,26 @@ use std::collections::HashMap; fn fruit_basket() -> HashMap { // TODO: Declare the hash map. - // let mut basket = + let mut basket =HashMap::new(); // Two bananas are already given for you :) - basket.insert(String::from("banana"), 2); + basket.insert("banana".to_string(), 2); // TODO: Put more fruits in your basket. + basket.insert("apples".to_string(), 2); + basket.insert("watermelon".to_string(), 2); + basket.insert("pinapple".to_string(), 2); + basket.insert("papaya".to_string(), 2); basket } fn main() { // You can optionally experiment here. + let a=vec![1,2,3]; + let b = a.into_iter().sum::(); + + print!("{b}") } #[cfg(test)] @@ -35,6 +43,6 @@ mod tests { #[test] fn at_least_five_fruits() { let basket = fruit_basket(); - assert!(basket.values().sum::() >= 5); + assert!(basket.into_values().sum::() >= 5); } } diff --git a/exercises/11_hashmaps/hashmaps2.rs b/exercises/11_hashmaps/hashmaps2.rs index e9f53fea3c..4b2d4ecd33 100644 --- a/exercises/11_hashmaps/hashmaps2.rs +++ b/exercises/11_hashmaps/hashmaps2.rs @@ -32,6 +32,11 @@ fn fruit_basket(basket: &mut HashMap) { // TODO: Insert new fruits if they are not already present in the // basket. Note that you are not allowed to put any type of fruit that's // already present! + match basket.get(&fruit) { + Some(_) => {}, + None => { basket.insert(fruit, 1); }, + } + } } diff --git a/exercises/11_hashmaps/hashmaps3.rs b/exercises/11_hashmaps/hashmaps3.rs index 5b390ab9b8..9f2e9bde15 100644 --- a/exercises/11_hashmaps/hashmaps3.rs +++ b/exercises/11_hashmaps/hashmaps3.rs @@ -6,10 +6,10 @@ // number of goals the team scored, and the total number of goals the team // conceded. -use std::collections::HashMap; +use std::{collections::HashMap}; // A structure to store the goal details of a team. -#[derive(Default)] +#[derive(Default,Debug)] struct TeamScores { goals_scored: u8, goals_conceded: u8, @@ -31,6 +31,14 @@ fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> { // Keep in mind that goals scored by team 1 will be the number of goals // conceded by team 2. Similarly, goals scored by team 2 will be the // number of goals conceded by team 1. + scores.entry(team_1_name) + .and_modify(|a| + {a.goals_scored+=team_1_score ; a.goals_conceded+=team_2_score} + ).or_insert(TeamScores { goals_scored: team_1_score, goals_conceded: team_2_score }); + scores.entry(team_2_name) + .and_modify(|a| + {a.goals_scored+=team_2_score ; a.goals_conceded+= team_1_score}) + .or_insert(TeamScores { goals_scored: team_2_score, goals_conceded: team_1_score }); } scores @@ -38,6 +46,19 @@ fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> { fn main() { // You can optionally experiment here. + + const RESULTS: &str = "England,France,4,2 + France,Italy,3,1 + Poland,Spain,2,0 + Germany,England,2,1 + England,Spain,1,0"; + + let mut scores = HashMap::<&str, TeamScores>::new(); + + scores.entry("test").or_default(); + scores.entry("test2").and_modify(|a| { a.goals_conceded += 1; }); + print!("{scores:?}") + } #[cfg(test)] diff --git a/exercises/12_options/options1.rs b/exercises/12_options/options1.rs index d0c412a8d4..5755dfb5b8 100644 --- a/exercises/12_options/options1.rs +++ b/exercises/12_options/options1.rs @@ -1,9 +1,15 @@ -// This function returns how much ice cream there is left in the fridge. +// This function returns how much icecream there is left in the fridge. // If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00, -// someone eats it all, so no ice cream is left (value 0). Return `None` if +// someone eats it all, so no icecream is left (value 0). Return `None` if // `hour_of_day` is higher than 23. -fn maybe_ice_cream(hour_of_day: u16) -> Option { +fn maybe_icecream(hour_of_day: u16) -> Option { // TODO: Complete the function body. + + match hour_of_day { + 0..22 => Some(5), + 22..24=>Some(0), + _=>None, + } } fn main() { @@ -18,19 +24,19 @@ mod tests { fn raw_value() { // TODO: Fix this test. How do you get the value contained in the // Option? - let ice_creams = maybe_ice_cream(12); + let icecreams = maybe_icecream(12).unwrap(); - assert_eq!(ice_creams, 5); // Don't change this line. + assert_eq!(icecreams, 5); // Don't change this line. } #[test] - fn check_ice_cream() { - assert_eq!(maybe_ice_cream(0), Some(5)); - assert_eq!(maybe_ice_cream(9), Some(5)); - assert_eq!(maybe_ice_cream(18), Some(5)); - assert_eq!(maybe_ice_cream(22), Some(0)); - assert_eq!(maybe_ice_cream(23), Some(0)); - assert_eq!(maybe_ice_cream(24), None); - assert_eq!(maybe_ice_cream(25), None); + fn check_icecream() { + assert_eq!(maybe_icecream(0), Some(5)); + assert_eq!(maybe_icecream(9), Some(5)); + assert_eq!(maybe_icecream(18), Some(5)); + assert_eq!(maybe_icecream(22), Some(0)); + assert_eq!(maybe_icecream(23), Some(0)); + assert_eq!(maybe_icecream(24), None); + assert_eq!(maybe_icecream(25), None); } } diff --git a/exercises/12_options/options2.rs b/exercises/12_options/options2.rs index 07c27c6ec2..1039d61db4 100644 --- a/exercises/12_options/options2.rs +++ b/exercises/12_options/options2.rs @@ -10,7 +10,7 @@ mod tests { let optional_target = Some(target); // TODO: Make this an if-let statement whose value is `Some`. - word = optional_target { + if let Some(word) = optional_target { assert_eq!(word, target); } } @@ -29,7 +29,7 @@ mod tests { // TODO: Make this a while-let statement. Remember that `Vec::pop()` // adds another layer of `Option`. You can do nested pattern matching // in if-let and while-let statements. - integer = optional_integers.pop() { + while let Some(Some(integer)) = optional_integers.pop() { assert_eq!(integer, cursor); cursor -= 1; } diff --git a/exercises/12_options/options3.rs b/exercises/12_options/options3.rs index c97b1d3cf4..c468e1d6a9 100644 --- a/exercises/12_options/options3.rs +++ b/exercises/12_options/options3.rs @@ -8,8 +8,8 @@ fn main() { let optional_point = Some(Point { x: 100, y: 200 }); // TODO: Fix the compiler error by adding something to this match statement. - match optional_point { - Some(p) => println!("Coordinates are {},{}", p.x, p.y), + match &optional_point { + Some(p) => println!("Co-ordinates are {},{}", p.x, p.y), _ => panic!("No match!"), } diff --git a/exercises/13_error_handling/README.md b/exercises/13_error_handling/README.md index 9b6674bc7a..3b21f2b782 100644 --- a/exercises/13_error_handling/README.md +++ b/exercises/13_error_handling/README.md @@ -1,8 +1,8 @@ # Error handling -Most errors aren't serious enough to require the program to stop entirely. -Sometimes, when a function fails, it's for a reason that you can easily interpret and respond to. -For example, if you try to open a file and that operation fails because the file doesn't exist, you might want to create the file instead of terminating the process. +Most errors aren’t serious enough to require the program to stop entirely. +Sometimes, when a function fails, it’s for a reason that you can easily interpret and respond to. +For example, if you try to open a file and that operation fails because the file doesn’t exist, you might want to create the file instead of terminating the process. ## Further information diff --git a/exercises/13_error_handling/errors1.rs b/exercises/13_error_handling/errors1.rs index e07fddc3cd..afabeda8dc 100644 --- a/exercises/13_error_handling/errors1.rs +++ b/exercises/13_error_handling/errors1.rs @@ -4,12 +4,12 @@ // construct to `Option` that can be used to express error conditions. Change // the function signature and body to return `Result` instead // of `Option`. -fn generate_nametag_text(name: String) -> Option { +fn generate_nametag_text(name: String) -> Result { if name.is_empty() { // Empty names aren't allowed - None + Err(format!("Empty names aren't allowed")) } else { - Some(format!("Hi! My name is {name}")) + Ok(format!("Hi! My name is {name}")) } } diff --git a/exercises/13_error_handling/errors2.rs b/exercises/13_error_handling/errors2.rs index defe359b48..578e9ff0f1 100644 --- a/exercises/13_error_handling/errors2.rs +++ b/exercises/13_error_handling/errors2.rs @@ -21,7 +21,8 @@ fn total_cost(item_quantity: &str) -> Result { let cost_per_item = 5; // TODO: Handle the error case as described above. - let qty = item_quantity.parse::(); + let qty = item_quantity.parse::()?; + Ok(qty * cost_per_item + processing_fee) } diff --git a/exercises/13_error_handling/errors3.rs b/exercises/13_error_handling/errors3.rs index 8e8c38a2a1..98a0408da8 100644 --- a/exercises/13_error_handling/errors3.rs +++ b/exercises/13_error_handling/errors3.rs @@ -15,7 +15,7 @@ fn total_cost(item_quantity: &str) -> Result { // TODO: Fix the compiler error by changing the signature and body of the // `main` function. -fn main() { +fn main() -> Result<(), ParseIntError> { let mut tokens = 100; let pretend_user_input = "8"; @@ -28,4 +28,7 @@ fn main() { tokens -= cost; println!("You now have {tokens} tokens."); } + + Ok(()) + } diff --git a/exercises/13_error_handling/errors4.rs b/exercises/13_error_handling/errors4.rs index ba01e54bf5..11d0e1df07 100644 --- a/exercises/13_error_handling/errors4.rs +++ b/exercises/13_error_handling/errors4.rs @@ -9,8 +9,14 @@ struct PositiveNonzeroInteger(u64); impl PositiveNonzeroInteger { fn new(value: i64) -> Result { + // TODO: This function shouldn't always return an `Ok`. - Ok(Self(value as u64)) + + match value { + x if x < 0 => Err(CreationError::Negative), + 0 => Err(CreationError::Zero), + x => Ok(Self(x as u64)), + } } } diff --git a/exercises/13_error_handling/errors5.rs b/exercises/13_error_handling/errors5.rs index 125779b867..7b9019afbc 100644 --- a/exercises/13_error_handling/errors5.rs +++ b/exercises/13_error_handling/errors5.rs @@ -6,7 +6,7 @@ // // In short, this particular use case for boxes is for when you want to own a // value and you care only that it is a type which implements a particular -// trait. To do so, the `Box` is declared as of type `Box` where +// trait. To do so, The `Box` is declared as of type `Box` where // `Trait` is the trait the compiler looks for on any value used in that // context. For this exercise, that context is the potential errors which // can be returned in a `Result`. @@ -48,7 +48,7 @@ impl PositiveNonzeroInteger { // TODO: Add the correct return type `Result<(), Box>`. What can we // use to describe both errors? Is there a trait which both errors implement? -fn main() { +fn main() -> Result<(), Box> { let pretend_user_input = "42"; let x: i64 = pretend_user_input.parse()?; println!("output={:?}", PositiveNonzeroInteger::new(x)?); diff --git a/exercises/13_error_handling/errors6.rs b/exercises/13_error_handling/errors6.rs index b1995e036a..e6e7e70760 100644 --- a/exercises/13_error_handling/errors6.rs +++ b/exercises/13_error_handling/errors6.rs @@ -25,7 +25,9 @@ impl ParsePosNonzeroError { } // TODO: Add another error conversion function here. - // fn from_parse_int(???) -> Self { ??? } + fn from_parse_int(err:ParseIntError) -> Self { + Self::ParseInt(err) + } } #[derive(PartialEq, Debug)] @@ -43,7 +45,7 @@ impl PositiveNonzeroInteger { fn parse(s: &str) -> Result { // TODO: change this to return an appropriate error instead of panicking // when `parse()` returns an error. - let x: i64 = s.parse().unwrap(); + let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?; Self::new(x).map_err(ParsePosNonzeroError::from_creation) } } diff --git a/exercises/14_generics/generics1.rs b/exercises/14_generics/generics1.rs index 87ed990b65..e13fd828f1 100644 --- a/exercises/14_generics/generics1.rs +++ b/exercises/14_generics/generics1.rs @@ -6,7 +6,7 @@ fn main() { // TODO: Fix the compiler error by annotating the type of the vector // `Vec`. Choose `T` as some integer type that can be created from // `u8` and `i8`. - let mut numbers = Vec::new(); + let mut numbers: Vec = Vec::new(); // Don't change the lines below. let n1: u8 = 42; diff --git a/exercises/14_generics/generics2.rs b/exercises/14_generics/generics2.rs index 8908725bf9..ba77625854 100644 --- a/exercises/14_generics/generics2.rs +++ b/exercises/14_generics/generics2.rs @@ -1,12 +1,12 @@ // This powerful wrapper provides the ability to store a positive integer value. // TODO: Rewrite it using a generic so that it supports wrapping ANY type. -struct Wrapper { - value: u32, +struct Wrapper { + value: T, } // TODO: Adapt the struct's implementation to be generic over the wrapped value. -impl Wrapper { - fn new(value: u32) -> Self { +impl Wrapper { + fn new(value: T) -> Self { Wrapper { value } } } diff --git a/exercises/21_macros/README.md b/exercises/21_macros/README.md index de7fb7ba3b..337816d6e6 100644 --- a/exercises/21_macros/README.md +++ b/exercises/21_macros/README.md @@ -10,6 +10,5 @@ of exercises to Rustlings, but is all about learning to write Macros. ## Further information -- [The Rust Book - Macros](https://doc.rust-lang.org/book/ch20-05-macros.html) +- [Macros](https://doc.rust-lang.org/book/ch19-06-macros.html) - [The Little Book of Rust Macros](https://veykril.github.io/tlborm/) -- [Rust by Example - macro_rules!](https://doc.rust-lang.org/rust-by-example/macros.html) diff --git a/exercises/22_clippy/clippy3.rs b/exercises/22_clippy/clippy3.rs index 7a3cb39006..4f78834918 100644 --- a/exercises/22_clippy/clippy3.rs +++ b/exercises/22_clippy/clippy3.rs @@ -4,11 +4,9 @@ #[rustfmt::skip] #[allow(unused_variables, unused_assignments)] fn main() { - let my_option: Option<&str> = None; - // Assume that you don't know the value of `my_option`. - // In the case of `Some`, we want to print its value. + let my_option: Option<()> = None; if my_option.is_none() { - println!("{}", my_option.unwrap()); + println!("{:?}", my_option.unwrap()); } let my_arr = &[ diff --git a/exercises/quizzes/quiz1.rs b/exercises/quizzes/quiz1.rs index 04fb2aaf8d..172009b08a 100644 --- a/exercises/quizzes/quiz1.rs +++ b/exercises/quizzes/quiz1.rs @@ -16,6 +16,13 @@ fn main() { // You can optionally experiment here. } +fn calculate_price_of_apples(cant:u32)->u32{ + if cant>40{ + return cant; + } + cant*2 +} + // Don't change the tests! #[cfg(test)] mod tests { diff --git a/exercises/quizzes/quiz2.rs b/exercises/quizzes/quiz2.rs index 2cddba907e..0451bfd91e 100644 --- a/exercises/quizzes/quiz2.rs +++ b/exercises/quizzes/quiz2.rs @@ -27,7 +27,15 @@ mod my_module { use super::Command; // TODO: Complete the function as described above. - // pub fn transformer(input: ???) -> ??? { ??? } + pub fn transformer(input: Vec<(String, Command)>) -> Vec { + input.into_iter().map(|(s, c)| { + match c { + Command::Uppercase => s.to_uppercase(), + Command::Trim => s.trim().to_string(), + Command::Append(size) => format!("{}{}", s, "bar".repeat(size)), + } + }).collect() + } } fn main() { @@ -37,12 +45,12 @@ fn main() { #[cfg(test)] mod tests { // TODO: What do we need to import to have `transformer` in scope? - // use ???; + use super::my_module::transformer; use super::Command; #[test] fn it_works() { - let input = vec![ + let input: Vec<(String, Command)> = vec![ ("hello".to_string(), Command::Uppercase), (" all roads lead to rome! ".to_string(), Command::Trim), ("foo".to_string(), Command::Append(1)), From f90ea4c65bd66153f48d0837b04331cf633969c5 Mon Sep 17 00:00:00 2001 From: BraCR10 Date: Thu, 10 Jul 2025 01:44:47 -0600 Subject: [PATCH 2/6] Changing some files --- .gitignore | 20 +- .rustlings-state.txt | 60 + .typos.toml | 7 - CHANGELOG.md | 976 ------------- CONTRIBUTING.md | 61 - Cargo.lock | 793 +---------- Cargo.toml | 278 +++- LICENSE | 22 - README.md | 8 +- build.rs | 5 - clippy.toml | 15 - dev-Cargo.toml | 1 - dev/Cargo.toml | 223 --- dev/rustlings-repo.txt | 1 - release-hook.sh | 16 - rust-analyzer.toml | 2 + rustlings-macros/Cargo.toml | 24 - rustlings-macros/info.toml | 1211 ----------------- rustlings-macros/src/lib.rs | 56 - solutions/01_variables/variables5.rs | 2 +- solutions/03_if/if1.rs | 6 +- .../06_move_semantics/move_semantics4.rs | 2 + solutions/09_strings/strings3.rs | 1 - solutions/11_hashmaps/hashmaps3.rs | 8 +- solutions/12_options/options3.rs | 5 +- solutions/13_error_handling/errors2.rs | 2 +- solutions/13_error_handling/errors6.rs | 15 - solutions/14_generics/generics2.rs | 28 +- solutions/15_traits/traits1.rs | 32 +- solutions/15_traits/traits2.rs | 27 +- solutions/15_traits/traits3.rs | 36 +- solutions/15_traits/traits4.rs | 35 +- solutions/15_traits/traits5.rs | 39 +- solutions/16_lifetimes/lifetimes1.rs | 24 +- solutions/16_lifetimes/lifetimes2.rs | 29 +- solutions/16_lifetimes/lifetimes3.rs | 18 +- solutions/17_tests/tests1.rs | 24 +- solutions/17_tests/tests2.rs | 22 +- solutions/17_tests/tests3.rs | 45 +- solutions/18_iterators/iterators1.rs | 26 +- solutions/18_iterators/iterators2.rs | 56 +- solutions/18_iterators/iterators3.rs | 86 +- solutions/18_iterators/iterators4.rs | 72 +- solutions/18_iterators/iterators5.rs | 168 +-- solutions/19_smart_pointers/arc1.rs | 45 +- solutions/19_smart_pointers/box1.rs | 47 +- solutions/19_smart_pointers/cow1.rs | 69 +- solutions/19_smart_pointers/rc1.rs | 102 +- solutions/20_threads/threads1.rs | 37 +- solutions/20_threads/threads2.rs | 41 +- solutions/20_threads/threads3.rs | 62 +- solutions/21_macros/macros1.rs | 10 +- solutions/21_macros/macros2.rs | 10 +- solutions/21_macros/macros3.rs | 13 +- solutions/21_macros/macros4.rs | 15 +- solutions/22_clippy/clippy1.rs | 17 +- solutions/22_clippy/clippy2.rs | 10 +- solutions/22_clippy/clippy3.rs | 31 +- solutions/23_conversions/as_ref_mut.rs | 60 +- solutions/23_conversions/from_into.rs | 136 +- solutions/23_conversions/from_str.rs | 117 +- solutions/23_conversions/try_from_into.rs | 193 +-- solutions/23_conversions/using_as.rs | 24 +- solutions/quizzes/quiz2.rs | 2 +- solutions/quizzes/quiz3.rs | 65 +- src/app_state.rs | 650 --------- src/cargo_toml.rs | 153 --- src/cmd.rs | 163 --- src/dev.rs | 46 - src/dev/check.rs | 398 ------ src/dev/new.rs | 148 -- src/dev/update.rs | 44 - src/embedded.rs | 168 --- src/exercise.rs | 211 --- src/info_file.rs | 119 -- src/init.rs | 224 --- src/list.rs | 134 -- src/list/scroll_state.rs | 104 -- src/list/state.rs | 424 ------ src/main.rs | 217 --- src/run.rs | 60 - src/term.rs | 310 ----- src/watch.rs | 190 --- src/watch/notify_event.rs | 132 -- src/watch/state.rs | 299 ---- src/watch/terminal_event.rs | 73 - tests/integration_tests.rs | 182 --- tests/test_exercises/dev/Cargo.toml | 11 - .../exercises/compilation_failure.rs | 3 - .../exercises/compilation_success.rs | 1 - tests/test_exercises/exercises/not_in_info.rs | 1 - .../test_exercises/exercises/test_failure.rs | 9 - .../test_exercises/exercises/test_success.rs | 9 - tests/test_exercises/info.toml | 19 - website/.gitignore | 7 - website/config.toml | 41 - website/content/_index.md | 21 - website/content/community-exercises/index.md | 73 - website/content/setup/index.md | 78 -- website/content/usage/index.md | 55 - website/input.css | 54 - website/justfile | 5 - website/package.json | 5 - website/static/images/happy_ferris.svg | 33 - website/static/images/panic.svg | 70 - website/static/images/rust_logo.svg | 61 - website/templates/404.html | 14 - website/templates/anchor-link.html | 2 - website/templates/base.html | 92 -- website/templates/index.html | 9 - website/templates/page.html | 39 - website/templates/shortcodes/details.html | 9 - 112 files changed, 362 insertions(+), 10501 deletions(-) create mode 100644 .rustlings-state.txt delete mode 100644 .typos.toml delete mode 100644 CHANGELOG.md delete mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE delete mode 100644 build.rs delete mode 100644 clippy.toml delete mode 120000 dev-Cargo.toml delete mode 100644 dev/Cargo.toml delete mode 100644 dev/rustlings-repo.txt delete mode 100755 release-hook.sh create mode 100644 rust-analyzer.toml delete mode 100644 rustlings-macros/Cargo.toml delete mode 100644 rustlings-macros/info.toml delete mode 100644 rustlings-macros/src/lib.rs delete mode 100644 src/app_state.rs delete mode 100644 src/cargo_toml.rs delete mode 100644 src/cmd.rs delete mode 100644 src/dev.rs delete mode 100644 src/dev/check.rs delete mode 100644 src/dev/new.rs delete mode 100644 src/dev/update.rs delete mode 100644 src/embedded.rs delete mode 100644 src/exercise.rs delete mode 100644 src/info_file.rs delete mode 100644 src/init.rs delete mode 100644 src/list.rs delete mode 100644 src/list/scroll_state.rs delete mode 100644 src/list/state.rs delete mode 100644 src/main.rs delete mode 100644 src/run.rs delete mode 100644 src/term.rs delete mode 100644 src/watch.rs delete mode 100644 src/watch/notify_event.rs delete mode 100644 src/watch/state.rs delete mode 100644 src/watch/terminal_event.rs delete mode 100644 tests/integration_tests.rs delete mode 100644 tests/test_exercises/dev/Cargo.toml delete mode 100644 tests/test_exercises/exercises/compilation_failure.rs delete mode 100644 tests/test_exercises/exercises/compilation_success.rs delete mode 100644 tests/test_exercises/exercises/not_in_info.rs delete mode 100644 tests/test_exercises/exercises/test_failure.rs delete mode 100644 tests/test_exercises/exercises/test_success.rs delete mode 100644 tests/test_exercises/info.toml delete mode 100644 website/.gitignore delete mode 100644 website/config.toml delete mode 100644 website/content/_index.md delete mode 100644 website/content/community-exercises/index.md delete mode 100644 website/content/setup/index.md delete mode 100644 website/content/usage/index.md delete mode 100644 website/input.css delete mode 100644 website/justfile delete mode 100644 website/package.json delete mode 100644 website/static/images/happy_ferris.svg delete mode 100644 website/static/images/panic.svg delete mode 100644 website/static/images/rust_logo.svg delete mode 100644 website/templates/404.html delete mode 100644 website/templates/anchor-link.html delete mode 100644 website/templates/base.html delete mode 100644 website/templates/index.html delete mode 100644 website/templates/page.html delete mode 100644 website/templates/shortcodes/details.html diff --git a/.gitignore b/.gitignore index ea65eb1ed8..9182e2f494 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,3 @@ -# Cargo -target/ Cargo.lock -!/Cargo.lock - -# State file -.rustlings-state.txt - -# OS -.DS_Store -.direnv/ - -# Editor -*.swp -.idea -*.iml - -# Ignore file for editors like Helix -.ignore +target/ +.vscode/ diff --git a/.rustlings-state.txt b/.rustlings-state.txt new file mode 100644 index 0000000000..c3678cb2a0 --- /dev/null +++ b/.rustlings-state.txt @@ -0,0 +1,60 @@ +DON'T EDIT THIS FILE! + +generics1 + +intro1 +intro2 +variables1 +variables2 +variables3 +variables4 +variables5 +variables6 +functions1 +functions2 +functions3 +functions4 +functions5 +if1 +if2 +if3 +quiz1 +primitive_types1 +primitive_types2 +primitive_types3 +primitive_types4 +primitive_types5 +primitive_types6 +vecs1 +vecs2 +move_semantics1 +move_semantics2 +move_semantics3 +move_semantics4 +move_semantics5 +structs1 +structs2 +structs3 +enums1 +enums2 +enums3 +strings1 +strings2 +strings3 +strings4 +modules1 +modules2 +modules3 +hashmaps1 +hashmaps2 +hashmaps3 +quiz2 +options1 +options2 +options3 +errors1 +errors2 +errors3 +errors4 +errors5 +errors6 \ No newline at end of file diff --git a/.typos.toml b/.typos.toml deleted file mode 100644 index 743c87416f..0000000000 --- a/.typos.toml +++ /dev/null @@ -1,7 +0,0 @@ -[default.extend-words] -"earch" = "earch" # Because of earch in the list footer - -[files] -extend-exclude = [ - "CHANGELOG.md", -] diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c1dbb42bbe..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,976 +0,0 @@ -## Unreleased - -### Changed - -- Upgrade to Rust edition 2024 -- Raise the minimum supported Rust version to `1.87` - -## 6.4.0 (2024-11-11) - -### Added - -- The list of exercises is now searchable by pressing `s` or `/` 🔍️ (thanks to [@frroossst](https://github.com/frroossst)) -- New option `c` in the prompt to manually check all exercises ✅ (thanks to [@Nahor](https://github.com/Nahor)) -- New command `check-all` to manually check all exercises ✅ (thanks to [@Nahor](https://github.com/Nahor)) -- Addictive animation for showing the progress of checking all exercises. A nice showcase of parallelism in Rust ✨ -- New option `x` in the prompt to reset the file of the current exercise 🔄 -- Allow `dead_code` for all exercises and solutions ⚰️ (thanks to [@huss4in](https://github.com/huss4in)) -- Pause input while running an exercise to avoid unexpected prompt interactions ⏸️ -- Limit the maximum number of exercises to 999. Any community exercises willing to reach that limit? 🔝 - -### Changed - -- `enums3`: Remove redundant enum definition task (thanks to [@senekor](https://github.com/senekor)) -- `if2`: Make the exercise less confusing by avoiding "fizz", "fuzz", "foo", "bar" and "baz" (thanks to [@senekor](https://github.com/senekor)) -- `hashmap3`: Use the method `Entry::or_default`. -- Update the state of all exercises when checking all of them (thanks to [@Nahor](https://github.com/Nahor)) -- The main prompt doesn't need a confirmation with ENTER on Unix-like systems anymore. -- No more jumping back to a previous exercise when its file is changed. Use the list to jump between exercises. -- Dump the solution file after an exercise is done even if the solution's directory doesn't exist. -- Rework the footer in the list. -- Optimize the file watcher. - -### Fixed - -- Fix bad contrast in the list on terminals with a light theme. - -## 6.3.0 (2024-08-29) - -### Added - -- Add the following exercise lints: - - `forbid(unsafe_code)`: You shouldn't write unsafe code in Rustlings. - - `forbid(unstable_features)`: You don't need unstable features in Rustlings and shouldn't rely on them while learning Rust. - - `forbid(todo)`: You forgot a `todo!()`. - - `forbid(empty_loop)`: This can only happen by mistake in Rustlings. - - `deny(infinite_loop)`: No infinite loops are needed in Rustlings. - - `deny(mem_forget)`: You shouldn't leak memory while still learning Rust. -- Show a link to every exercise file in the list. -- Add scroll padding in the list. -- Break the help footer of the list into two lines when the terminal width isn't big enough. -- Enable scrolling with the mouse in the list. -- `dev check`: Show the progress of checks. -- `dev check`: Check that the length of all exercise names is lower than 32. -- `dev check`: Check if exercise contains no tests and isn't marked with `test = false`. - -### Changed - -- The compilation time when installing Rustlings is reduced. -- Pressing `c` in the list for "continue on" now quits the list after setting the selected exercise as the current one. -- Better highlighting of the solution file after an exercise is done. -- Don't show the output of successful tests anymore. Instead, show the pretty output for tests. -- Be explicit about `q` only quitting the list and not the whole program in the list. -- Be explicit about `r` only resetting one exercise (the selected one) in the list. -- Ignore the standard output of `git init`. -- `threads3`: Remove the queue length and improve tests. -- `errors4`: Use match instead of a comparison chain in the solution. -- `functions3`: Only take `u8` to avoid using a too high number of iterations by mistake. -- `dev check`: Always check with strict Clippy (warnings to errors) when checking the solutions. - -### Fixed - -- Fix the error on some systems about too many open files during the final check of all exercises. -- Fix the list when the terminal height is too low. -- Restore the terminal after an error in the list. - -## 6.2.0 (2024-08-09) - -### Added - -- Show a message before checking and running an exercise. This gives the user instant feedback and avoids confusion if the checks take too long. -- Show a helpful error message when trying to install Rustlings with a Rust version lower than the minimum one that Rustlings supports. -- Add a `README.md` file to the `solutions/` directory. -- Allow initializing Rustlings in a Cargo workspace. -- `dev check`: Check that all solutions are formatted with `rustfmt`. - -### Changed - -- Remove the state file and the solutions directory from the generated `.gitignore` file. -- Run the final check of all exercises in parallel. -- Small exercise improvements. - -## 6.1.0 (2024-07-10) - -#### Added - -- `dev check`: Check that all exercises (including community ones) include at least one `TODO` comment. -- `dev check`: Check that all exercises actually fail to run (not already solved). - -#### Changed - -- Make enum variants more consistent between enum exercises. -- `iterators3`: Teach about the possible case of integer overflow during division. - -#### Fixed - -- Exit with a helpful error message on missing/unsupported terminal/TTY. -- Mark the last exercise as done. - -## 6.0.1 (2024-07-04) - -Small exercise improvements and fixes. -Most importantly, fixed that the exercise `clippy1` was already solved 😅 - -## 6.0.0 (2024-07-03) - -This release is the result of a complete rewrite to deliver a ton of new features and improvements ✨ -The most important changes are highlighted below. - -### Installation - -The installation has been simplified a lot! -To install Rustlings after installing Rust, all what you need to do now is running the following command: - -```bash -cargo install rustlings -``` - -Yes, this means that Rustlings is now on [crates.io](https://crates.io/crates/rustlings) 🎉 - -You can read about the motivations of this change in [this issue](https://github.com/rust-lang/rustlings/issues/1919). - -### UI/UX - -- The UI is now responsive when the terminal is resized. -- The progress bar was moved to the bottom so that you can always see your progress and the current exercise to work on. -- The current exercise path is now a terminal link. It will open the exercise file in your default editor when you click on it. -- A small prompt is now always shown at the bottom. It allows you to choose an action by entering a character. For example, entering `h` will show you the hint of the current exercise. -- The comment "I AM NOT DONE!" doesn't exist anymore. Instead of needing to remove it to go to the next exercise, you need to enter `n` in the terminal. - -### List mode - -A new list mode was added! -You can enter it by entering `l` in the watch mode. -It offers the following features: - -- Browse all exercises and see their state (pending/done). -- Filter exercises based on their state (pending/done). -- Continue at another exercise. This allows you to skip some exercises or go back to previous ones. -- Reset an exercise so you can start over and revert your changes. - -### Solutions - -After finishing an exercise, a solution file will be available and Rustlings will show you its path in green. -This allows you to compare your solution with an idiomatic solution and maybe learn about other ways to solve a problem. - -While writing the solutions, all exercises have been polished 🌟 -For example, every exercise now contains `TODO` comments to highlight what the user needs to change and where. - -### LSP support out of the box - -Instead of creating a `project.json` file using `rustlings lsp`, Rustlings now works with a `Cargo.toml` file out of the box. -No actions are needed to activate the language server `rust-analyzer`. - -This should avoid issues related to the language server or to running exercises, especially the ones with Clippy. - -### Clippy - -Clippy lints are now shown on all exercises, not only the Clippy exercises 📎 -Make Clippy your friend from early on 🥰 - -### Community Exercises - -Rustlings now supports community exercises! - -Do you want to create your own set of Rustlings exercises to focus on some specific topic? -Or do you want to translate the original Rustlings exercises? -Then follow the link to the guide about [community exercises](https://rustlings.rust-lang.org/community-exercises)! - -## 5.6.1 (2023-09-18) - -#### Changed - -- Converted all exercises with assertions to test mode. - -#### Fixed - -- `cow1`: Reverted regression introduced by calling `to_mut` where it - shouldn't have been called, and clarified comment. -- `primitive_types3`: Require at least an array of 100 elements. -- Removed hint comments when no hint exists for the exercise. -- `as_ref_mut`: Fixed a typo in a test function name. -- `enums3`: Fixed formatting with `rustfmt`. - -## 5.6.0 (2023-09-04) - -#### Added - -- New exercise: `if3`, teaching the user about `if let` statements. -- `hashmaps2`: Added an extra test function to check if the amount of fruits is higher than zero. -- `enums3`: Added a test for `Message`. -- `if1`: Added a test case to check equal values. -- `if3`: Added a note specifying that there are no test changes needed. - -#### Changed - -- Swapped the order of threads and smart pointer exercises. -- Rewrote the CLI to use `clap` - it's matured much since we switched to `argh` :) -- `structs3`: Switched from i32 to u32. -- `move_semantics`: Switched 1-4 to tests, and rewrote them to be way simpler, while still teaching about the same - concepts. - -#### Fixed - -- `iterators5`: - - Removed an outdated part of the hint. - - Renamed variables to use snake_case. -- `vecs2`: Updated the hint to reference the renamed loop variable. -- `enums3`: Changed message string in test so that it gets properly tested. -- `strings2`: Corrected line number in hint, then removed it (this both happened as part of this release cycle). -- `primitive_types4`: Updated hint to the correct ending index. -- `quiz1`: Removed duplicated sentence from exercise comments. -- `errors4`: Improved comment. -- `from_into`: Fixed test values. -- `cow1`: Added `.to_mut()` to distinguish from the previous test case. -- `threads2`: Updated hint text to reference the correct book heading. - -#### Housekeeping - -- Cleaned up the explanation paragraphs at the start of each exercise. -- Lots of Nix housekeeping that I don't feel qualified to write about! -- Improved CI workflows, we're now testing on multiple platforms at once. - -## 5.5.1 (2023-05-17) - -#### Fixed - -- Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix. - -## 5.5.0 (2023-05-17) - -#### Added - -- `strings2`: Added a reference to the book chapter for reference conversion -- `lifetimes`: Added a link to the lifetimekata project -- Added a new `tests4` exercises, which teaches about testing for panics -- Added a `!` prefix command to watch mode that runs an external command -- Added a `--success-hints` option to watch mode that shows hints on exercise success - -#### Changed - -- `vecs2`: Renamed iterator variable bindings for clarify -- `lifetimes`: Changed order of book references -- `hashmaps2`: Clarified instructions in the todo block -- Moved lifetime exercises before test exercises (via the recommended book ordering) -- `options2`: Improved tests for layering options -- `modules2`: Added more information to the hint - -#### Fixed - -- `errors2`: Corrected a comment wording -- `iterators2`: Fixed a spelling mistake in the hint text -- `variables`: Wrapped the mut keyword with backticks for readability -- `move_semantics2`: Removed references to line numbers -- `cow1`: Clarified the `owned_no_mutation` comments -- `options3`: Changed exercise to panic when no match is found -- `rustlings lsp` now generates absolute paths, which should fix VSCode `rust-analyzer` usage on Windows - -#### Housekeeping - -- Added a markdown linter to run on GitHub actions -- Split quick installation section into two code blocks - -## 5.4.1 (2023-03-10) - -#### Changed - -- `vecs`: Added links to `iter_mut` and `map` to README.md -- `cow1`: Changed main to tests -- `iterators1`: Formatted according to rustfmt - -#### Fixed - -- `errors5`: Unified undisclosed type notation -- `arc1`: Improved readability by avoiding implicit dereference -- `macros4`: Prevented auto-fix by adding `#[rustfmt::skip]` -- `cli`: Actually show correct progress percentages - -## 5.4.0 (2023-02-12) - -#### Changed - -- Reordered exercises - - Unwrapped `standard_library_types` into `iterators` and `smart_pointers` - - Moved smart pointer exercises behind threads - - Ordered `rc1` before `arc1` -- **intro1**: Added a note on `rustlings lsp` -- **threads1**: Panic if threads are not joined -- **cli**: - - Made progress bar update proportional to amount of files verified - - Decreased `watch` delay from 2 to 1 second - -#### Fixed - -- Capitalized "Rust" in exercise hints -- **enums3**: Removed superfluous tuple brackets -- **quiz2, clippy1, iterators1**: Fixed a typo -- **rc1**: Fixed a prompt error -- **cli**: - - Fixed a typo in a method name - - Specified the edition in `rustc` commands - -#### Housekeeping - -- Bumped min Rust version to 1.58 in installation script - -## 5.3.0 (2022-12-23) - -#### Added - -- **cli**: Added a percentage display in watch mode -- Added a `flake.nix` for Nix users - -#### Changed - -- **structs3**: Added an additional test -- **macros**: Added a link to MacroKata in the README - -#### Fixed - -- **strings3**: Added a link to `std` in the hint -- **threads1**: Corrected a hint link -- **iterators1**: Clarified hint steps -- **errors5**: Fix a typo in the hint -- **options1**: Clarified on the usage of the 24-hour system -- **threads2, threads3**: Explicitly use `Arc::clone` -- **structs3**: Clarifed the hint -- **quiz2, as_ref_mut, options1, traits1, traits2**: Clarified hints -- **traits1, traits2, cli**: Tidied up unmatching backticks -- **enums2**: Removed unnecessary indirection of self -- **enums3**: Added an extra tuple comment - -#### Housekeeping - -- Added a VSCode extension recommendation -- Applied some Clippy and rustfmt formatting -- Added a note on Windows PowerShell and other shell compatibility - -## 5.2.1 (2022-09-06) - -#### Fixed - -- **quiz1**: Reworded the comment to actually reflect what's going on in the tests. - Also added another assert just to make sure. -- **rc1**: Fixed a typo in the hint. -- **lifetimes**: Add quotes to the `println!` output, for readability. - -#### Housekeeping - -- Fixed a typo in README.md - -## 5.2.0 (2022-08-27) - -#### Added - -- Added a `reset` command - -#### Changed - -- **options2**: Convert the exercise to use tests - -#### Fixed - -- **threads3**: Fixed a typo -- **quiz1**: Adjusted the explanations to be consistent with - the tests - -## 5.1.1 (2022-08-17) - -#### Bug Fixes - -- Fixed an incorrect assertion in options1 - -## 5.1.0 (2022-08-16) - -#### Features - -- Added a new `rc1` exercise. -- Added a new `cow1` exercise. - -#### Bug Fixes - -- **variables5**: Corrected reference to previous exercise -- **functions4**: Fixed line number reference -- **strings3**: Clarified comment wording -- **traits4, traits5**: Fixed line number reference -- **traits5**: - - Fixed typo in "parameter" - - Made exercise prefer a traits-based solution -- **lifetimes2**: Improved hint -- **threads3**: Fixed typo in hint -- **box1**: Replaced `unimplemented!` with `todo!` -- **errors5**: Provided an explanation for usage of `Box` -- **quiz2**: Fixed a typo -- **macros**: Updated the macros book link -- **options1**: - - Removed unused code - - Added more granular tests -- Fixed some comment syntax shenanigans in info.toml - -#### Housekeeping - -- Fixed a typo in .editorconfig -- Fixed a typo in integration_tests.rs -- Clarified manual installation instructions using `cargo install --path .` -- Added a link to our Zulip in the readme file - -## 5.0.0 (2022-07-16) - -#### Features - -- Hint comments in exercises now also include a reference to the - `hint` watch mode subcommand. -- **intro1**: Added more hints to point the user to the source file. -- **variables**: Switched variables3 and variables4. -- Moved `vec` and `primitive_types` exercises before `move_semantics`. -- Renamed `vec` to `vecs` to be more in line with the naming in general. -- Split up the `collections` exercises in their own folders. -- **vec2**: Added a second part of the function that provides an alternative, - immutable way of modifying vec values. -- **enums3**: Added a hint. -- Moved `strings` before `modules`. -- Added a `strings3` exercise to teach modifying strings. -- Added a `hashmaps3` exercise for some advanced usage of hashmaps. -- Moved the original `quiz2` to be `strings4`, since it only tested strings - anyways. -- Reworked `quiz2` into a new exercise that tests more chapters. -- Renamed `option` to `options`. -- **options1**: Rewrote parts of the exercise to remove the weird array - iteration stuff. -- Moved `generics3` to be `quiz3`. -- Moved box/arc exercises behind `iterators`. -- **iterators4**: Added a test for factorials of zero. -- Split `threads1` between two exercises, the first one focusing more on - `JoinHandle`s. -- Added a `threads3` exercises that uses `std::sync::mpsc`. -- Added a `clippy3` exercises with some more interesting checks. -- **as_ref_mut**: Added a section that actually tests `AsMut`. -- Added 3 new lifetimes exercises. -- Added 3 new traits exercises. - -#### Bug Fixes - -- **variables2**: Made output messages more verbose. -- **variables5**: Added a nudging hint about shadowing. -- **variables6**: Fixed link to book. -- **functions**: Clarified the README wording. Generally cleaned up - some hints and added some extra comments. -- **if2**: Renamed function name to `foo_if_fizz`. -- **move_semantics**: Clarified some hints. -- **quiz1**: Renamed the function name to be more verbose. -- **structs1**: Use an integer type instead of strings. Renamed "unit structs" - to "unit-like structs", as is used in the book. -- **structs3**: Added the `panic!` statement in from the beginning. -- **errors1**: Use `is_empty()` instead of `len() > 0` -- **errors3**: Improved the hint. -- **errors5**: Improved exercise instructions and the hint. -- **errors6**: Provided the skeleton of one of the functions that's supposed - to be implemented. -- **iterators3**: Inserted `todo!` into `divide()` to keep a compiler error - from happening. -- **from_str**: Added a hint comment about string error message conversion with - `Box`. -- **try_from_into**: Fixed the function name in comment. - -#### Removed - -- Removed the legacy LSP feature that was using `mod.rs` files. -- Removed `quiz4`. -- Removed `advanced_errs`. These were the last exercises in the recommended - order, and I've always felt like they didn't quite fit in with the mostly - simple, book-following style we've had in Rustlings. - -#### Housekeeping - -- Added missing exercises to the book index. -- Updated spacing in Cargo.toml. -- Added a GitHub actions config so that tests run on every PR/commit. - -## 4.8.0 (2022-07-01) - -#### Features - -- Added a progress indicator for `rustlings watch`. -- The installation script now checks for Rustup being installed. -- Added a `rustlings lsp` command to enable `rust-analyzer`. - -#### Bug Fixes - -- **move_semantics5**: Replaced "in vogue" with "in scope" in hint. -- **if2**: Fixed a typo in the hint. -- **variables1**: Fixed an incorrect line reference in the hint. -- Fixed an out of bounds check in the installation Bash script. - -#### Housekeeping - -- Replaced the git.io URL with the fully qualified URL because of git.io's sunsetting. -- Removed the deprecated Rust GitPod extension. - -## 4.7.1 (2022-04-20) - -#### Features - -- The amount of dependency crates that need to be compiled went down from ~65 to - ~45 by bumping dependency versions. -- The minimum Rust version in the install scripts has been bumped to 1.56.0 (this isn't in - the release itself, since install scripts don't really get versioned) - -#### Bug Fixes - -- **arc1**: A small part has been rewritten using a more functional code style (#968). -- **using_as**: A small part has been refactored to use `sum` instead of `fold`, resulting - in better readability. - -#### Housekeeping - -- The changelog will now be manually written instead of being automatically generated by the - Git log. - -## 4.7.0 (2022-04-14) - -#### Features - -- Add move_semantics6.rs exercise (#908) ([3f0e1303](https://github.com/rust-lang/rustlings/commit/3f0e1303e0b3bf3fecc0baced3c8b8a37f83c184)) -- **intro:** Add intro section. ([21c9f441](https://github.com/rust-lang/rustlings/commit/21c9f44168394e08338fd470b5f49b1fd235986f)) -- Include exercises folder in the project structure behind a feature, enabling rust-analyzer to work (#917) ([179a75a6](https://github.com/rust-lang/rustlings/commit/179a75a68d03ac9518dec2297fb17f91a4fc506b)) - -#### Bug Fixes - -- Fix a few spelling mistakes ([1c0fe3cb](https://github.com/rust-lang/rustlings/commit/1c0fe3cbcca85f90b3985985b8e265ee872a2ab2)) -- **cli:** - - Move long text strings into constants. ([f78c4802](https://github.com/rust-lang/rustlings/commit/f78c48020830d7900dd8d81f355606581670446d)) - - Replace `filter_map()` with `find_map()` ([9b27e8d](https://github.com/rust-lang/rustlings/commit/9b27e8d993ca20232fe38a412750c3f845a83b65)) -- **clippy1:** - - Set clippy::float_cmp lint to deny (#907) ([71a06044](https://github.com/rust-lang/rustlings/commit/71a06044e6a96ff756dc31d7b0ed665ae4badb57)) - - Updated code to test correctness clippy lint with approx_constant lint rule ([f2650de3](https://github.com/rust-lang/rustlings/commit/f2650de369810867d2763e935ac0963c32ec420e)) -- **errors1:** - - Add a comment to make the purpose more clear (#486) ([cbcde345](https://github.com/rust-lang/rustlings/commit/cbcde345409c3e550112e449242848eaa3391bb6)) - - Don't modify tests (#958) ([60bb7cc](https://github.com/rust-lang/rustlings/commit/60bb7cc3931d21d3986ad52b2b302e632a93831c)) -- **errors6:** Remove existing answer code ([43d0623](https://github.com/rust-lang/rustlings/commit/43d0623086edbc46fe896ba59c7afa22c3da9f7a)) -- **functions5:** Remove wrong new line and small English improvements (#885) ([8ef4869b](https://github.com/rust-lang/rustlings/commit/8ef4869b264094e5a9b50452b4534823a9df19c3)) -- **install:** protect path with whitespaces using quotes and stop at the first error ([d114847f](https://github.com/rust-lang/rustlings/commit/d114847f256c5f571c0b4c87e04b04bce3435509)) -- **intro1:** Add compiler error explanation. ([9b8de655](https://github.com/rust-lang/rustlings/commit/9b8de65525a5576b78cf0c8e4098cdd34296338f)) -- **iterators1:** reorder TODO steps ([0bd7a063](https://github.com/rust-lang/rustlings/commit/0bd7a0631a17a9d69af5746795a30efc9cf64e6e)) -- **move_semantics2:** Add comment ([89650f80](https://github.com/rust-lang/rustlings/commit/89650f808af23a32c9a2c6d46592b77547a6a464)) -- **move_semantics5:** correct typo (#857) ([46c28d5c](https://github.com/rust-lang/rustlings/commit/46c28d5cef3d8446b5a356b19d8dbc725f91a3a0)) -- **quiz1:** update to say quiz covers "If" ([1622e8c1](https://github.com/rust-lang/rustlings/commit/1622e8c198d89739765c915203efff0091bdeb78)) -- **structs3:** - - Add a hint for panic (#608) ([4f7ff5d9](https://github.com/rust-lang/rustlings/commit/4f7ff5d9c7b2d8b045194c1a9469d37e30257c4a)) - - remove redundant 'return' (#852) ([bf33829d](https://github.com/rust-lang/rustlings/commit/bf33829da240375d086f96267fc2e02fa6b07001)) - - Assigned value to `cents_per_gram` in test ([d1ee2da](https://github.com/rust-lang/rustlings/commit/d1ee2daf14f19105e6db3f9c610f44293d688532)) -- **structs3.rs:** assigned value to cents_per_gram in test ([d1ee2daf](https://github.com/rust-lang/rustlings/commit/d1ee2daf14f19105e6db3f9c610f44293d688532)) -- **traits1:** rename test functions to snake case (#854) ([1663a16e](https://github.com/rust-lang/rustlings/commit/1663a16eade6ca646b6ed061735f7982434d530d)) - -#### Documentation improvements - -- Add hints on how to get GCC installed (#741) ([bc56861](https://github.com/rust-lang/rustlings/commit/bc5686174463ad6f4f6b824b0e9b97c3039d4886)) -- Fix some code blocks that were not highlighted ([17f9d74](https://github.com/rust-lang/rustlings/commit/17f9d7429ccd133a72e815fb5618e0ce79560929)) - -## 4.6.0 (2021-09-25) - -#### Features - -- add advanced_errs2 ([abd6b70c](https://github.com/rust-lang/rustlings/commit/abd6b70c72dc6426752ff41f09160b839e5c449e)) -- add advanced_errs1 ([882d535b](https://github.com/rust-lang/rustlings/commit/882d535ba8628d5e0b37e8664b3e2f26260b2671)) -- Add a farewell message when quitting `watch` ([1caef0b4](https://github.com/rust-lang/rustlings/commit/1caef0b43494c8b8cdd6c9260147e70d510f1aca)) -- add more watch commands ([a7dc080b](https://github.com/rust-lang/rustlings/commit/a7dc080b95e49146fbaafe6922a6de2f8cb1582a), closes [#842](https://github.com/rust-lang/rustlings/issues/842)) -- **modules:** update exercises, add modules3 (#822) ([dfd2fab4](https://github.com/rust-lang/rustlings/commit/dfd2fab4f33d1bf59e2e5ee03123c0c9a67a9481)) -- **quiz1:** add default function name in comment (#838) ([0a11bad7](https://github.com/rust-lang/rustlings/commit/0a11bad71402b5403143d642f439f57931278c07)) - -#### Bug Fixes - -- Correct small typo in exercises/conversions/from_str.rs ([86cc8529](https://github.com/rust-lang/rustlings/commit/86cc85295ae36948963ae52882e285d7e3e29323)) -- **cli:** typo in exercise.rs (#848) ([06d5c097](https://github.com/rust-lang/rustlings/commit/06d5c0973a3dffa3c6c6f70acb775d4c6630323c)) -- **from_str, try_from_into:** custom error types ([2dc93cad](https://github.com/rust-lang/rustlings/commit/2dc93caddad43821743e4903d89b355df58d7a49)) -- **modules2:** fix typo (#835) ([1c3beb0a](https://github.com/rust-lang/rustlings/commit/1c3beb0a59178c950dc05fe8ee2346b017429ae0)) -- **move_semantics5:** - - change &mut \*y to &mut x (#814) ([d75759e8](https://github.com/rust-lang/rustlings/commit/d75759e829fdcd64ef071cf4b6eae2a011a7718b)) - - Clarify instructions ([df25684c](https://github.com/rust-lang/rustlings/commit/df25684cb79f8413915e00b5efef29369849cef1)) -- **quiz1:** Fix inconsistent wording (#826) ([03131a3d](https://github.com/rust-lang/rustlings/commit/03131a3d35d9842598150f9da817f7cc26e2669a)) - -## 4.5.0 (2021-07-07) - -#### Features - -- Add move_semantics5 exercise. (#746) ([399ab328](https://github.com/rust-lang/rustlings/commit/399ab328d8d407265c09563aa4ef4534b2503ff2)) -- **cli:** Add "next" to run the next unsolved exercise. (#785) ([d20e413a](https://github.com/rust-lang/rustlings/commit/d20e413a68772cd493561f2651cf244e822b7ca5)) - -#### Bug Fixes - -- rename result1 to errors4 ([50ab289d](https://github.com/rust-lang/rustlings/commit/50ab289da6b9eb19a7486c341b00048c516b88c0)) -- move_semantics5 hints ([1b858285](https://github.com/rust-lang/rustlings/commit/1b85828548f46f58b622b5e0c00f8c989f928807)) -- remove trailing whitespaces from iterators1 ([4d4fa774](https://github.com/rust-lang/rustlings/commit/4d4fa77459392acd3581c6068aa8be9a02de12fc)) -- add hints to generics1 and generics2 exercises ([31457940](https://github.com/rust-lang/rustlings/commit/31457940846b3844d78d4a4d2b074bc8d6aaf1eb)) -- remove trailing whitespace ([d9b69bd1](https://github.com/rust-lang/rustlings/commit/d9b69bd1a0a7a99f2c0d80933ad2eea44c8c71b2)) -- **installation:** first PowerShell command ([aa9a943d](https://github.com/rust-lang/rustlings/commit/aa9a943ddf3ae260782e73c26bcc9db60e5894b6)) -- **iterators5:** derive Clone, Copy ([91fc9e31](https://github.com/rust-lang/rustlings/commit/91fc9e3118f4af603c9911698cc2a234725cb032)) -- **quiz1:** Updated question description (#794) ([d8766496](https://github.com/rust-lang/rustlings/commit/d876649616cc8a8dd5f539f8bc1a5434b960b1e9)) -- **try_from_into, from_str:** hints for dyn Error ([11d2cf0d](https://github.com/rust-lang/rustlings/commit/11d2cf0d604dee3f5023c17802d69438e69fa50e)) -- **variables5:** confine the answer further ([48ffcbd2](https://github.com/rust-lang/rustlings/commit/48ffcbd2c4cc4d936c2c7480019190f179813cc5)) - -## 4.4.0 (2021-04-24) - -#### Bug Fixes - -- Fix spelling error in main.rs ([91ee27f2](https://github.com/rust-lang/rustlings/commit/91ee27f22bd3797a9db57e5fd430801c170c5db8)) -- typo in default out text ([644c49f1](https://github.com/rust-lang/rustlings/commit/644c49f1e04cbb24e95872b3a52b07d692ae3bc8)) -- **collections:** Naming exercises for vectors and hashmap ([bef39b12](https://github.com/rust-lang/rustlings/commit/bef39b125961310b34b34871e480a82e82af4678)) -- **from_str:** - - Correct typos ([5f7c89f8](https://github.com/rust-lang/rustlings/commit/5f7c89f85db1f33da01911eaa479c3a2d4721678)) - - test for error instead of unwrap/should_panic ([15e71535](https://github.com/rust-lang/rustlings/commit/15e71535f37cfaed36e22eb778728d186e2104ab)) - - use trait objects for from_str ([c3e7b831](https://github.com/rust-lang/rustlings/commit/c3e7b831786c9172ed8bd5d150f3c432f242fba9)) -- **functions3:** improve function argument type (#687) ([a6509cc4](https://github.com/rust-lang/rustlings/commit/a6509cc4d545d8825f01ddf7ee37823b372154dd)) -- **hashmap2:** Update incorrect assertion (#660) ([72aaa15e](https://github.com/rust-lang/rustlings/commit/72aaa15e6ab4b72b3422f1c6356396e20a2a2bb8)) -- **info:** Fix typo (#635) ([cddc1e86](https://github.com/rust-lang/rustlings/commit/cddc1e86e7ec744ee644cc774a4887b1a0ded3e8)) -- **iterators2:** Moved errors out of tests. ([baf4ba17](https://github.com/rust-lang/rustlings/commit/baf4ba175ba6eb92989e3dd54ecbec4bedc9a863), closes [#359](https://github.com/rust-lang/rustlings/issues/359)) -- **iterators3:** Enabled iterators3.rs to run without commented out tests. ([c6712dfc](https://github.com/rust-lang/rustlings/commit/c6712dfccd1a093e590ad22bbc4f49edc417dac0)) -- **main:** Let find_exercise work with borrows ([347f30bd](https://github.com/rust-lang/rustlings/commit/347f30bd867343c5ace1097e085a1f7e356553f7)) -- **move_semantics4:** - - Remove redundant "instead" (#640) ([cc266d7d](https://github.com/rust-lang/rustlings/commit/cc266d7d80b91e79df3f61984f231b7f1587218e)) - - Small readbility improvement (#617) ([10965920](https://github.com/rust-lang/rustlings/commit/10965920fbdf8a1efc85bed869e55a1787006404)) -- **option2:** Rename uninformative variables (#675) ([b4de6594](https://github.com/rust-lang/rustlings/commit/b4de6594380636817d13c2677ec6f472a964cf43)) -- **quiz3:** Force an answer to Q2 (#672) ([0d894e6f](https://github.com/rust-lang/rustlings/commit/0d894e6ff739943901e1ae8c904582e5c2f843bd)) -- **structs:** Add 5.3 to structs/README (#652) ([6bd791f2](https://github.com/rust-lang/rustlings/commit/6bd791f2f44aa7f0ad926df767f6b1fa8f12a9a9)) -- **structs2:** correct grammar in hint (#663) ([ebdb66c7](https://github.com/rust-lang/rustlings/commit/ebdb66c7bfb6d687a14cc511a559a222e6fc5de4)) -- **structs3:** - - reword heading comment (#664) ([9f3e8c2d](https://github.com/rust-lang/rustlings/commit/9f3e8c2dde645e5264c2d2200e68842b5f47bfa3)) - - add check to prevent naive implementation of is_international ([05a753fe](https://github.com/rust-lang/rustlings/commit/05a753fe6333d36dbee5f68c21dec04eacdc75df)) -- **threads1:** line number correction ([7857b0a6](https://github.com/rust-lang/rustlings/commit/7857b0a689b0847f48d8c14cbd1865e3b812d5ca)) -- **try_from_into:** use trait objects ([2e93a588](https://github.com/rust-lang/rustlings/commit/2e93a588e0abe8badb7eafafb9e7d073c2be5df8)) - -#### Features - -- Replace clap with argh ([7928122f](https://github.com/rust-lang/rustlings/commit/7928122fcef9ca7834d988b1ec8ca0687478beeb)) -- Replace emojis when NO_EMOJI env variable present ([8d62a996](https://github.com/rust-lang/rustlings/commit/8d62a9963708dbecd9312e8bcc4b47049c72d155)) -- Added iterators5.rs exercise. ([b29ea17e](https://github.com/rust-lang/rustlings/commit/b29ea17ea94d1862114af2cf5ced0e09c197dc35)) -- **arc1:** Add more details to description and hint (#710) ([81be4044](https://github.com/rust-lang/rustlings/commit/81be40448777fa338ebced3b0bfc1b32d6370313)) -- **cli:** Improve the list command with options, and then some ([8bbe4ff1](https://github.com/rust-lang/rustlings/commit/8bbe4ff1385c5c169c90cd3ff9253f9a91daaf8e)) -- **list:** - - updated progress percentage ([1c6f7e4b](https://github.com/rust-lang/rustlings/commit/1c6f7e4b7b9b3bd36f4da2bb2b69c549cc8bd913)) - - added progress info ([c0e3daac](https://github.com/rust-lang/rustlings/commit/c0e3daacaf6850811df5bc57fa43e0f249d5cfa4)) - -## 4.3.0 (2020-12-29) - -#### Features - -- Rewrite default out text ([44d39112](https://github.com/rust-lang/rustlings/commit/44d39112ff122b29c9793fe52e605df1612c6490)) -- match exercise order to book chapters (#541) ([033bf119](https://github.com/rust-lang/rustlings/commit/033bf1198fc8bfce1b570e49da7cde010aa552e3)) -- Crab? (#586) ([fa9f522b](https://github.com/rust-lang/rustlings/commit/fa9f522b7f043d7ef73a39f003a9272dfe72c4f4)) -- add "rustlings list" command ([838f9f30](https://github.com/rust-lang/rustlings/commit/838f9f30083d0b23fd67503dcf0fbeca498e6647)) -- **try_from_into:** remove duplicate annotation ([04f1d079](https://github.com/rust-lang/rustlings/commit/04f1d079aa42a2f49af694bc92c67d731d31a53f)) - -#### Bug Fixes - -- update structs README ([bcf14cf6](https://github.com/rust-lang/rustlings/commit/bcf14cf677adb3a38a3ac3ca53f3c69f61153025)) -- added missing exercises to info.toml ([90cfb6ff](https://github.com/rust-lang/rustlings/commit/90cfb6ff28377531bfc34acb70547bdb13374f6b)) -- gives a bit more context to magic number ([30644c9a](https://github.com/rust-lang/rustlings/commit/30644c9a062b825c0ea89435dc59f0cad86b110e)) -- **functions2:** Change signature to trigger precise error message: (#605) ([0ef95947](https://github.com/rust-lang/rustlings/commit/0ef95947cc30482e63a7045be6cc2fb6f6dcb4cc)) -- **structs1:** Adjust wording (#573) ([9334783d](https://github.com/rust-lang/rustlings/commit/9334783da31d821cc59174fbe8320df95828926c)) -- **try_from_into:** - - type error ([4f4cfcf3](https://github.com/rust-lang/rustlings/commit/4f4cfcf3c36c8718c7c170c9c3a6935e6ef0618c)) - - Update description (#584) ([96347df9](https://github.com/rust-lang/rustlings/commit/96347df9df294f01153b29d9ad4ba361f665c755)) -- **vec1:** Have test compare every element in a and v ([9b6c6293](https://github.com/rust-lang/rustlings/commit/9b6c629397b24b944f484f5b2bbd8144266b5695)) - -## 4.2.0 (2020-11-07) - -#### Features - -- Add HashMap exercises ([633c00cf](https://github.com/rust-lang/rustlings/commit/633c00cf8071e1e82959a3010452a32f34f29fc9)) -- Add Vec exercises ([0c12fa31](https://github.com/rust-lang/rustlings/commit/0c12fa31c57c03c6287458a0a8aca7afd057baf6)) -- **primitive_types6:** Add a test (#548) ([2b1fb2b7](https://github.com/rust-lang/rustlings/commit/2b1fb2b739bf9ad8d6b7b12af25fee173011bfc4)) -- **try_from_into:** Add tests (#571) ([95ccd926](https://github.com/rust-lang/rustlings/commit/95ccd92616ae79ba287cce221101e0bbe4f68cdc)) - -#### Bug Fixes - -- log error output when inotify limit is exceeded ([d61b4e5a](https://github.com/rust-lang/rustlings/commit/d61b4e5a13b44d72d004082f523fa1b6b24c1aca)) -- more unique temp_file ([5643ef05](https://github.com/rust-lang/rustlings/commit/5643ef05bc81e4a840e9456f4406a769abbe1392)) -- **installation:** Update the MinRustVersion ([21bfb2d4](https://github.com/rust-lang/rustlings/commit/21bfb2d4777429c87d8d3b5fbf0ce66006dcd034)) -- **iterators2:** Update description (#578) ([197d3a3d](https://github.com/rust-lang/rustlings/commit/197d3a3d8961b2465579218a6749b2b2cefa8ddd)) -- **primitive_types6:** - - remove 'unused doc comment' warning ([472d8592](https://github.com/rust-lang/rustlings/commit/472d8592d65c8275332a20dfc269e7ac0d41bc88)) - - missing comma in test ([4fb230da](https://github.com/rust-lang/rustlings/commit/4fb230daf1251444fcf29e085cee222a91f8a37e)) -- **quiz3:** Second test is for odd numbers, not even. (#553) ([18e0bfef](https://github.com/rust-lang/rustlings/commit/18e0bfef1de53071e353ba1ec5837002ff7290e6)) - -## 4.1.0 (2020-10-05) - -#### Bug Fixes - -- Update rustlings version in Cargo.lock ([1cc40bc9](https://github.com/rust-lang/rustlings/commit/1cc40bc9ce95c23d56f6d91fa1c4deb646231fef)) -- **arc1:** index mod should equal thread count ([b4062ef6](https://github.com/rust-lang/rustlings/commit/b4062ef6993e80dac107c4093ea85166ad3ee0fa)) -- **enums3:** Update Message::ChangeColor to take a tuple. (#457) ([4b6540c7](https://github.com/rust-lang/rustlings/commit/4b6540c71adabad647de8a09e57295e7c7c7d794)) -- **exercises:** adding question mark to quiz2 ([101072ab](https://github.com/rust-lang/rustlings/commit/101072ab9f8c80b40b8b88cb06cbe38aca2481c5)) -- **generics3:** clarify grade change ([47f7672c](https://github.com/rust-lang/rustlings/commit/47f7672c0307732056e7426e81d351f0dd7e22e5)) -- **structs3:** Small adjustment of variable name ([114b54cb](https://github.com/rust-lang/rustlings/commit/114b54cbdb977234b39e5f180d937c14c78bb8b2)) -- **using_as:** Add test so that proper type is returned. (#512) ([3286c5ec](https://github.com/rust-lang/rustlings/commit/3286c5ec19ea5fb7ded81d047da5f8594108a490)) - -#### Features - -- Added iterators1.rs exercise ([9642f5a3](https://github.com/rust-lang/rustlings/commit/9642f5a3f686270a4f8f6ba969919ddbbc4f7fdd)) -- Add ability to run rustlings on repl.it (#471) ([8f7b5bd0](https://github.com/rust-lang/rustlings/commit/8f7b5bd00eb83542b959830ef55192d2d76db90a)) -- Add gitpod support (#473) ([4821a8be](https://github.com/rust-lang/rustlings/commit/4821a8be94af4f669042a06ab917934cfacd032f)) -- Remind the user of the hint option (#425) ([816b1f5e](https://github.com/rust-lang/rustlings/commit/816b1f5e85d6cc6e72673813a85d0ada2a8f84af)) -- Remind the user of the hint option (#425) ([9f61db5d](https://github.com/rust-lang/rustlings/commit/9f61db5dbe38538cf06571fcdd5f806e7901e83a)) -- **cli:** Added 'cls' command to 'watch' mode (#474) ([4f2468e1](https://github.com/rust-lang/rustlings/commit/4f2468e14f574a93a2e9b688367b5752ed96ae7b)) -- **try_from_into:** Add insufficient length test (#469) ([523d18b8](https://github.com/rust-lang/rustlings/commit/523d18b873a319f7c09262f44bd40e2fab1830e5)) - -## 4.0.0 (2020-07-08) - -#### Breaking Changes - -- Add a --nocapture option to display test harnesses' outputs ([8ad5f9bf](https://github.com/rust-lang/rustlings/commit/8ad5f9bf531a4848b1104b7b389a20171624c82f)) -- Rename test to quiz, fixes #244 ([010a0456](https://github.com/rust-lang/rustlings/commit/010a04569282149cea7f7a76fc4d7f4c9f0f08dd)) - -#### Features - -- Add traits README ([173bb141](https://github.com/rust-lang/rustlings/commit/173bb14140c5530cbdb59e53ace3991a99d804af)) -- Add box1.rs exercise ([7479a473](https://github.com/rust-lang/rustlings/commit/7479a4737bdcac347322ad0883ca528c8675e720)) -- Rewrite try_from_into (#393) ([763aa6e3](https://github.com/rust-lang/rustlings/commit/763aa6e378a586caae2d8d63755a85eeba227933)) -- Add if2 exercise ([1da84b5f](https://github.com/rust-lang/rustlings/commit/1da84b5f7c489f65bd683c244f13c7d1ee812df0)) -- Added exercise structs3.rs ([b66e2e09](https://github.com/rust-lang/rustlings/commit/b66e2e09622243e086a0f1258dd27e1a2d61c891)) -- Add exercise variables6 covering const (#352) ([5999acd2](https://github.com/rust-lang/rustlings/commit/5999acd24a4f203292be36e0fd18d385887ec481)) - -#### Bug Fixes - -- Change then to than ([ddd98ad7](https://github.com/rust-lang/rustlings/commit/ddd98ad75d3668fbb10eff74374148aa5ed2344d)) -- rename quiz1 to tests1 in info (#420) ([0dd1c6ca](https://github.com/rust-lang/rustlings/commit/0dd1c6ca6b389789e0972aa955fe17aa15c95f29)) -- fix quiz naming inconsistency (#421) ([5563adbb](https://github.com/rust-lang/rustlings/commit/5563adbb890587fc48fbbc9c4028642687f1e85b)) -- confine the user further in variable exercises ([06ef4cc6](https://github.com/rust-lang/rustlings/commit/06ef4cc654e75d22a526812919ee49b8956280bf)) -- update iterator and macro text for typos and clarity ([95900828](https://github.com/rust-lang/rustlings/commit/959008284834bece0196a01e17ac69a7e3590116)) -- update generics2 closes #362 ([964c974a](https://github.com/rust-lang/rustlings/commit/964c974a0274199d755073b917c2bc5da0c9b4f1)) -- confusing comment in conversions/try_from_into.rs ([c9e4f2cf](https://github.com/rust-lang/rustlings/commit/c9e4f2cfb4c48d0b7451263cfb43b9426438122d)) -- **arc1:** Passively introduce attributes (#429) ([113cdae2](https://github.com/rust-lang/rustlings/commit/113cdae2d4e4c55905e8056ad326ede7fd7de356)) -- **box1:** fix comment typo (#426) ([bb2ca251](https://github.com/rust-lang/rustlings/commit/bb2ca251106b27a7272d9a30872904dd1376654c)) -- **errorsn:** Try harder to confine the user. (#388) ([2b20c8a0](https://github.com/rust-lang/rustlings/commit/2b20c8a0f5774d07c58d110d75879f33fc6273b5)) -- **from_into.rs:** typo ([a901499e](https://github.com/rust-lang/rustlings/commit/a901499ededd3ce1995164700514fe4e9a0373ea)) -- **generics2:** Guide students to the answer (#430) ([e6bd8021](https://github.com/rust-lang/rustlings/commit/e6bd8021d9a7dd06feebc30c9d5f953901d7b419)) -- **installation:** - - Provide a backup git reference when tag can't be curl ([9e4fb100](https://github.com/rust-lang/rustlings/commit/9e4fb1009f1c9e3433915c03e22c2af422e5c5fe)) - - Check if python is available while checking for git,rustc and cargo ([9cfb617d](https://github.com/rust-lang/rustlings/commit/9cfb617d5b0451b4b51644a1298965390cda9884)) -- **option1:** - - Don't add only zeros to the numbers array ([cce6a442](https://github.com/rust-lang/rustlings/commit/cce6a4427718724a9096800754cd3abeca6a1580)) - - Add cast to usize, as it is confusing in the context of an exercise about Option ([f6cffc7e](https://github.com/rust-lang/rustlings/commit/f6cffc7e487b42f15a6f958e49704c93a8d4465b)) -- **option2:** Add TODO to comments (#400) ([10967bce](https://github.com/rust-lang/rustlings/commit/10967bce57682812dc0891a9f9757da1a9d87404)) -- **options1:** Add hint about Array Initialization (#389) ([9f75554f](https://github.com/rust-lang/rustlings/commit/9f75554f2a30295996f03f0160b98c0458305502)) -- **test2:** name of type String and &str (#394) ([d6c0a688](https://github.com/rust-lang/rustlings/commit/d6c0a688e6a96f93ad60d540d4b326f342fc0d45)) -- **variables6:** minor typo (#419) ([524e17df](https://github.com/rust-lang/rustlings/commit/524e17df10db95f7b90a0f75cc8997182a8a4094)) - -## 3.0.0 (2020-04-11) - -#### Breaking Changes - -- make "compile" exercises print output (#278) ([3b6d5c](https://github.com/fmoko/rustlings/commit/3b6d5c3aaa27a242a832799eb66e96897d26fde3)) - -#### Bug Fixes - -- **primitive_types:** revert primitive_types4 (#296) ([b3a3351e](https://github.com/rust-lang/rustlings/commit/b3a3351e8e6a0bdee07077d7b0382953821649ae)) -- **run:** compile clippy exercise files (#295) ([3ab084a4](https://github.com/rust-lang/rustlings/commit/3ab084a421c0f140ae83bf1fc3f47b39342e7373)) -- **conversions:** - - add additional test to meet exercise rules (#284) ([bc22ec3](https://github.com/fmoko/rustlings/commit/bc22ec382f843347333ef1301fc1bad773657f38)) - - remove duplicate not done comment (#292) ([dab90f](https://github.com/fmoko/rustlings/commit/dab90f7b91a6000fe874e3d664f244048e5fa342)) -- don't hardcode documentation version for traits (#288) ([30e6af](https://github.com/fmoko/rustlings/commit/30e6af60690c326fb5d3a9b7335f35c69c09137d)) - -#### Features - -- add Option2 exercise (#290) ([86b5c08b](https://github.com/rust-lang/rustlings/commit/86b5c08b9bea1576127a7c5f599f5752072c087d)) -- add exercise for option (#282) ([135e5d47](https://github.com/rust-lang/rustlings/commit/135e5d47a7c395aece6f6022117fb20c82f2d3d4)) -- add new exercises for generics (#280) ([76be5e4e](https://github.com/rust-lang/rustlings/commit/76be5e4e991160f5fd9093f03ee2ba260e8f7229)) -- **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71)) - -### 2.2.1 (2020-02-27) - -#### Bug Fixes - -- Re-add cloning the repo to install scripts ([3d9b03c5](https://github.com/rust-lang/rustlings/commit/3d9b03c52b8dc51b140757f6fd25ad87b5782ef5)) - -#### Features - -- Add clippy lints (#269) ([1e2fd9c9](https://github.com/rust-lang/rustlings/commit/1e2fd9c92f8cd6e389525ca1a999fca4c90b5921)) - -## 2.2.0 (2020-02-25) - -#### Bug Fixes - -- Update deps to version compatible with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401)) -- **docs:** - - Added a necessary step to Windows installation process (#242) ([3906efcd](https://github.com/rust-lang/rustlings/commit/3906efcd52a004047b460ed548037093de3f523f)) - - Fixed mangled sentence from book; edited for clarity (#266) ([ade52ff](https://github.com/rust-lang/rustlings/commit/ade52ffb739987287ddd5705944c8777705faed9)) - - Updated iterators readme to account for iterators4 exercise (#273) ([bec8e3a](https://github.com/rust-lang/rustlings/commit/bec8e3a644cbd88db1c73ea5f1d8a364f4a34016)) -- **installation:** make fatal errors more obvious (#272) ([17d0951e](https://github.com/rust-lang/rustlings/commit/17d0951e66fda8e11b204d5c4c41a0d5e22e78f7)) -- **iterators2:** - - Remove reference to missing iterators2.rs (#245) ([419f7797](https://github.com/rust-lang/rustlings/commit/419f7797f294e4ce6a2b883199731b5bde77d262)) -- **as_ref_mut:** Enable a test and improve per clippy's suggestion (#256) ([dfdf809](https://github.com/rust-lang/rustlings/commit/dfdf8093ebbd4145864995627b812780de52f902)) -- **tests1:** - - Change test command ([fe10e06c](https://github.com/rust-lang/rustlings/commit/fe10e06c3733ddb4a21e90d09bf79bfe618e97ce) - - Correct test command in tests1.rs comment (#263) ([39fa7ae](https://github.com/rust-lang/rustlings/commit/39fa7ae8b70ad468da49b06f11b2383135a63bcf)) - -#### Features - -- Add variables5.rs exercise (#264) ([0c73609e](https://github.com/rust-lang/rustlings/commit/0c73609e6f2311295e95d6f96f8c747cfc4cba03)) -- Show a completion message when watching (#253) ([d25ee55a](https://github.com/rust-lang/rustlings/commit/d25ee55a3205882d35782e370af855051b39c58c)) -- Add type conversion and parsing exercises (#249) ([0c85dc11](https://github.com/rust-lang/rustlings/commit/0c85dc1193978b5165491b99cc4922caf8d14a65)) -- Created consistent money unit (#258) ([fd57f8f](https://github.com/rust-lang/rustlings/commit/fd57f8f2c1da2af8ddbebbccec214e6f40f4dbab)) -- Enable test for exercise test4 (#276) ([8b971ff](https://github.com/rust-lang/rustlings/commit/8b971ffab6079a706ac925f5917f987932b55c07)) -- Added traits exercises (#274 but specifically #216, which originally added - this :heart:) ([b559cdd](https://github.com/rust-lang/rustlings/commit/b559cdd73f32c0d0cfc1feda39f82b3e3583df17)) - -## 2.1.0 (2019-11-27) - -#### Bug Fixes - -- add line numbers in several exercises and hints ([b565c4d3](https://github.com/rust-lang/rustlings/commit/b565c4d3e74e8e110bef201a082fa1302722a7c3)) -- **arc1:** Fix some words in the comment ([c42c3b21](https://github.com/rust-lang/rustlings/commit/c42c3b2101df9164c8cd7bb344def921e5ba3e61)) -- **enums:** Add link to chapter on pattern syntax (#242) ([615ce327](https://github.com/rust-lang/rustlings/commit/615ce3279800c56d89f19d218ccb7ef576624feb)) -- **primitive_types4:** - - update outdated hint ([4c5189df](https://github.com/rust-lang/rustlings/commit/4c5189df2bdd9a231f6b2611919ba5aa14da0d3f)) - - update outdated comment ([ded2c034](https://github.com/rust-lang/rustlings/commit/ded2c034ba93fa1e3c2c2ea16b83abc1a57265e8)) -- **strings2:** update line number in hint ([a09f684f](https://github.com/rust-lang/rustlings/commit/a09f684f05c58d239a6fc59ec5f81c2533e8b820)) -- **variables1:** Correct wrong word in comment ([fda5a470](https://github.com/rust-lang/rustlings/commit/fda5a47069e0954f16a04e8e50945e03becb71a5)) - -#### Features - -- **watch:** show hint while watching ([8143d57b](https://github.com/rust-lang/rustlings/commit/8143d57b4e88c51341dd4a18a14c536042cc009c)) - -## 2.0.0 (2019-11-12) - -#### Bug Fixes - -- **default:** Clarify the installation procedure ([c371b853](https://github.com/rust-lang/rustlings/commit/c371b853afa08947ddeebec0edd074b171eeaae0)) -- **info:** Fix trailing newlines for hints ([795b6e34](https://github.com/rust-lang/rustlings/commit/795b6e348094a898e9227a14f6232f7bb94c8d31)) -- **run:** make `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3)) - -#### Breaking Changes - -- Refactor hint system ([9bdb0a12](https://github.com/rust-lang/rustlings/commit/9bdb0a12e45a8e9f9f6a4bd4a9c172c5376c7f60)) -- improve `watch` execution mode ([2cdd6129](https://github.com/rust-lang/rustlings/commit/2cdd61294f0d9a53775ee24ad76435bec8a21e60)) -- Index exercises by name ([627cdc07](https://github.com/rust-lang/rustlings/commit/627cdc07d07dfe6a740e885e0ddf6900e7ec336b)) -- **run:** makes `run` never prompt ([4b265465](https://github.com/rust-lang/rustlings/commit/4b26546589f7d2b50455429482cf1f386ceae8b3)) - -#### Features - -- **cli:** check for rustc before doing anything ([36a033b8](https://github.com/rust-lang/rustlings/commit/36a033b87a6549c1e5639c908bf7381c84f4f425)) -- **hint:** Add test for hint ([ce9fa6eb](https://github.com/rust-lang/rustlings/commit/ce9fa6ebbfdc3e7585d488d9409797285708316f)) - -### 1.5.1 (2019-11-11) - -#### Bug Fixes - -- **errors3:** Update hint ([dcfb427b](https://github.com/rust-lang/rustlings/commit/dcfb427b09585f0193f0a294443fdf99f11c64cb), closes [#185](https://github.com/rust-lang/rustlings/issues/185)) -- **if1:** Remove `return` reference ([ad03d180](https://github.com/rust-lang/rustlings/commit/ad03d180c9311c0093e56a3531eec1a9a70cdb45)) -- **strings:** Move Strings before Structs ([6dcecb38](https://github.com/rust-lang/rustlings/commit/6dcecb38a4435593beb87c8e12d6314143631482), closes [#204](https://github.com/rust-lang/rustlings/issues/204)) -- **structs1:** Remove misleading comment ([f72e5a8f](https://github.com/rust-lang/rustlings/commit/f72e5a8f05568dde04eaeac10b9a69872f21cb37)) -- **threads:** Move Threads behind SLT ([fbe91a67](https://github.com/rust-lang/rustlings/commit/fbe91a67a482bfe64cbcdd58d06ba830a0f39da3), closes [#205](https://github.com/rust-lang/rustlings/issues/205)) -- **watch:** clear screen before each `verify()` ([3aff590](https://github.com/rust-lang/rustlings/commit/3aff59085586c24196a547c2693adbdcf4432648)) - -## 1.5.0 (2019-11-09) - -#### Bug Fixes - -- **test1:** Rewrite logic ([79a56942](https://github.com/rust-lang/rustlings/commit/79a569422c8309cfc9e4aed25bf4ab3b3859996b)) -- **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6)) -- **iterators:** Rename iterator3.rs ([433d2115](https://github.com/rust-lang/rustlings/commit/433d2115bc1c04b6d34a335a18c9a8f3e2672bc6)) -- **iterators2:** Remove syntax resulting in misleading error message ([4cde8664](https://github.com/rust-lang/rustlings/commit/4cde86643e12db162a66e62f23b78962986046ac)) -- **option1:** - - Fix arguments passed to assert! macro (#222) ([4c2cf6da](https://github.com/rust-lang/rustlings/commit/4c2cf6da755efe02725e05ecc3a303304c10a6da)) - - Fix arguments passed to assert! macro ([ead4f7af](https://github.com/rust-lang/rustlings/commit/ead4f7af9e10e53418efdde5c359159347282afd)) - - Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6)) -- **primitive_types4:** Fail on a slice covering the wrong area ([5b1e673c](https://github.com/rust-lang/rustlings/commit/5b1e673cec1658afc4ebbbc800213847804facf5)) -- **readme:** http to https ([70946b85](https://github.com/rust-lang/rustlings/commit/70946b85e536e80e70ed9505cb650ca0a3a1fbb5)) -- **test1:** - - Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446)) - - renamed function name to snake case closes #180 ([89d5186c](https://github.com/rust-lang/rustlings/commit/89d5186c0dae8135ecabf90ee8bb35949bc2d29b)) - -#### Features - -- Add enums exercises ([dc150321](https://github.com/rust-lang/rustlings/commit/dc15032112fc485226a573a18139e5ce928b1755)) -- Added exercise for struct update syntax ([1c4c8764](https://github.com/rust-lang/rustlings/commit/1c4c8764ed118740cd4cee73272ddc6cceb9d959)) -- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) - -### 1.4.1 (2019-08-13) - -#### Bug Fixes - -- **iterators2:** Remove syntax resulting in misleading error message ([4cde8664](https://github.com/rust-lang/rustlings/commit/4cde86643e12db162a66e62f23b78962986046ac)) -- **option1:** Add test for prematurely passing exercise ([a750e4a1](https://github.com/rust-lang/rustlings/commit/a750e4a1a3006227292bb17d57d78ce84da6bfc6)) -- **test1:** Swap assertion parameter order ([4086d463](https://github.com/rust-lang/rustlings/commit/4086d463a981e81d97781851d17db2ced290f446)) - -## 1.4.0 (2019-07-13) - -#### Bug Fixes - -- **installation:** Fix rustlings installation check ([7a252c47](https://github.com/rust-lang/rustlings/commit/7a252c475551486efb52f949b8af55803b700bc6)) -- **iterators:** Rename iterator3.rs ([433d2115](https://github.com/rust-lang/rustlings/commit/433d2115bc1c04b6d34a335a18c9a8f3e2672bc6)) -- **readme:** http to https ([70946b85](https://github.com/rust-lang/rustlings/commit/70946b85e536e80e70ed9505cb650ca0a3a1fbb5)) -- **test1:** renamed function name to snake case ([89d5186c](https://github.com/rust-lang/rustlings/commit/89d5186c0dae8135ecabf90ee8bb35949bc2d29b)) -- **cli:** Check if changed exercise file exists before calling verify ([ba85ca3](https://github.com/rust-lang/rustlings/commit/ba85ca32c4cfc61de46851ab89f9c58a28f33c88)) -- **structs1:** Fix the irrefutable let pattern warning ([cc6a141](https://github.com/rust-lang/rustlings/commit/cc6a14104d7c034eadc98297eaaa972d09c50b1f)) - -#### Features - -- **changelog:** Use clog for changelogs ([34e31232](https://github.com/rust-lang/rustlings/commit/34e31232dfddde284a341c9609b33cd27d9d5724)) -- **iterators2:** adds iterators2 exercise including config ([9288fccf](https://github.com/rust-lang/rustlings/commit/9288fccf07a2c5043b76d0fd6491e4cf72d76031)) - -### 1.3.0 (2019-06-05) - -#### Features - -- Adds a simple exercise for structures (#163, @briankung) - -#### Bug Fixes - -- Add Result type signature as it is difficult for new comers to understand Generics and Error all at once. (#157, @veggiemonk) -- Rustfmt and whitespace fixes (#161, @eddyp) -- errorsn.rs: Separate also the hints from each other to avoid accidental viewing (#162, @eddyp) -- fixed outdated links (#165, @gushroom) -- Fix broken link (#164, @HanKruiger) -- Remove highlighting and syntect (#167, @komaeda) - -### 1.2.2 (2019-05-07) - -#### Bug Fixes - -- Reverted `--nocapture` flag since it was causing tests to pass unconditionally - -### 1.2.1 (2019-04-22) - -#### Bug Fixes - -- Fix the `--nocapture` feature (@komaeda) -- Provide a nicer error message for when you're in the wrong directory - -### 1.2.0 (2019-04-22) - -#### Features - -- Add errors to exercises that compile without user changes (@yvan-sraka) -- Use --nocapture when testing, enabling `println!` when running (@komaeda) - -### 1.1.1 (2019-04-14) - -#### Bug fixes - -- Fix permissions on exercise files (@zacanger, #133) -- Make installation checks more thorough (@komaeda, 1b3469f236bc6979c27f6e1a04e4138a88e55de3) -- Fix order of true/false in tests for executables (@mgeier, #137) -- Stop run from panicking when compile fails (@cjpearce, #141) -- Fix intermittent test failure caused by race condition (@cjpearce, #140) -- Fix links by deleting book version (@diodfr, #142) -- Canonicalize paths to fix path matching (@cjpearce, #143) - -### 1.1.0 (2019-03-20) - -- errors2.rs: update link to Rust book (#124) -- Start verification at most recently modified file (#120) -- Watch for file creation events in watch mode (#117) -- Add standard library types to exercises suite (#119) -- Give a warning when Rustlings isn't run from the right directory (#123) -- Verify that rust version is recent enough to install Rustlings (#131) - -### 1.0.1 (2019-03-06) - -- Adds a way to install Rustlings in one command (`curl -L https://git.io/rustlings | bash`) -- Makes `rustlings watch` react to create file events (@shaunbennett, #117) -- Reworks the exercise management to use an external TOML file instead of just listing them in the code - -### 1.0.0 (2019-03-06) - -Initial release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 95605f70a7..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,61 +0,0 @@ -# Contributing to Rustlings - -First off, thanks for taking the time to contribute! ❤️ - -## Quick Reference - -I want to … - -- _report a bug!_ ➡️ [open an issue](#issues) -- _fix a bug!_ ➡️ [open a pull request](#pull-requests) -- _implement a new feature!_ ➡️ [open an issue to discuss it first, then a pull request](#issues) -- _add an exercise!_ ➡️ [read this](#adding-an-exercise) -- _update an outdated exercise!_ ➡️ [open a pull request](#pull-requests) - -## Issues - -You can open an issue [here](https://github.com/rust-lang/rustlings/issues/new). -If you're reporting a bug, please include the output of the following commands: - -- `cargo --version` -- `rustlings --version` -- `ls -la` -- Your OS name and version - -## Pull Requests - -You are welcome to open a pull request, but unless it is small and trivial, **please open an issue to discuss your idea first** 🙏🏼 - -Opening a pull request is as easy as forking the repository and committing your changes. -If you need any help with it or face any Git related problems, don't hesitate to ask for help 🤗 - -It may take time to review your pull request. -Please be patient 😇 - -When updating an exercise, check if its solution needs to be updated. - -## Adding An Exercise - -- Name the file `exercises/yourTopic/yourTopicN.rs`. -- Make sure to put in some helpful links, and link to sections of The Book in `exercises/yourTopic/README.md`. -- In the exercise, add a `// TODO: …` comment where user changes are required. -- Add a solution at `solutions/yourTopic/yourTopicN.rs` with comments explaining it. -- Add the [metadata for your exercise](#exercise-metadata) in the `rustlings-macros/info.toml` file. -- Make sure your exercise runs with `rustlings run yourTopicN`. -- [Open a pull request](#pull-requests). - -### Exercise Metadata - -The exercise metadata should contain the following: - -```toml -[[exercises]] -name = "yourTopicN" -dir = "yourTopic" -hint = """ -A useful (multi-line) hint for your exercise. -Include links to a section in The Book or a documentation page.""" -``` - -If your exercise doesn't contain any test, add `test = false` to the exercise metadata. -But adding tests is recommended. diff --git a/Cargo.lock b/Cargo.lock index 7ea1217048..503514a329 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,794 +3,5 @@ version = 4 [[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "4.5.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "crossterm" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" -dependencies = [ - "bitflags 2.9.1", - "crossterm_winapi", - "document-features", - "mio", - "parking_lot", - "rustix", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - -[[package]] -name = "document-features" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" -dependencies = [ - "litrs", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "filetime" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys", -] - -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - -[[package]] -name = "hashbrown" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "indexmap" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "inotify" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" -dependencies = [ - "bitflags 2.9.1", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "kqueue" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "libc" -version = "0.2.172" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.9.1", - "libc", - "redox_syscall", -] - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "notify" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" -dependencies = [ - "bitflags 2.9.1", - "filetime", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "notify-types", - "walkdir", - "windows-sys", -] - -[[package]] -name = "notify-types" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "redox_syscall" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustlings" -version = "6.4.0" -dependencies = [ - "anyhow", - "clap", - "crossterm", - "notify", - "rustix", - "rustlings-macros", - "serde", - "serde_json", - "tempfile", - "toml_edit", -] - -[[package]] -name = "rustlings-macros" -version = "6.4.0" -dependencies = [ - "quote", - "serde", - "toml_edit", -] - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys", -] - -[[package]] -name = "toml_datetime" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +name = "exercises" +version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 27531b37bf..8f09643e0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,65 +1,199 @@ -[workspace] -resolver = "2" -exclude = [ - "tests/test_exercises", - "dev", +bin = [ + { name = "intro1", path = "exercises/00_intro/intro1.rs" }, + { name = "intro1_sol", path = "solutions/00_intro/intro1.rs" }, + { name = "intro2", path = "exercises/00_intro/intro2.rs" }, + { name = "intro2_sol", path = "solutions/00_intro/intro2.rs" }, + { name = "variables1", path = "exercises/01_variables/variables1.rs" }, + { name = "variables1_sol", path = "solutions/01_variables/variables1.rs" }, + { name = "variables2", path = "exercises/01_variables/variables2.rs" }, + { name = "variables2_sol", path = "solutions/01_variables/variables2.rs" }, + { name = "variables3", path = "exercises/01_variables/variables3.rs" }, + { name = "variables3_sol", path = "solutions/01_variables/variables3.rs" }, + { name = "variables4", path = "exercises/01_variables/variables4.rs" }, + { name = "variables4_sol", path = "solutions/01_variables/variables4.rs" }, + { name = "variables5", path = "exercises/01_variables/variables5.rs" }, + { name = "variables5_sol", path = "solutions/01_variables/variables5.rs" }, + { name = "variables6", path = "exercises/01_variables/variables6.rs" }, + { name = "variables6_sol", path = "solutions/01_variables/variables6.rs" }, + { name = "functions1", path = "exercises/02_functions/functions1.rs" }, + { name = "functions1_sol", path = "solutions/02_functions/functions1.rs" }, + { name = "functions2", path = "exercises/02_functions/functions2.rs" }, + { name = "functions2_sol", path = "solutions/02_functions/functions2.rs" }, + { name = "functions3", path = "exercises/02_functions/functions3.rs" }, + { name = "functions3_sol", path = "solutions/02_functions/functions3.rs" }, + { name = "functions4", path = "exercises/02_functions/functions4.rs" }, + { name = "functions4_sol", path = "solutions/02_functions/functions4.rs" }, + { name = "functions5", path = "exercises/02_functions/functions5.rs" }, + { name = "functions5_sol", path = "solutions/02_functions/functions5.rs" }, + { name = "if1", path = "exercises/03_if/if1.rs" }, + { name = "if1_sol", path = "solutions/03_if/if1.rs" }, + { name = "if2", path = "exercises/03_if/if2.rs" }, + { name = "if2_sol", path = "solutions/03_if/if2.rs" }, + { name = "if3", path = "exercises/03_if/if3.rs" }, + { name = "if3_sol", path = "solutions/03_if/if3.rs" }, + { name = "quiz1", path = "exercises/quizzes/quiz1.rs" }, + { name = "quiz1_sol", path = "solutions/quizzes/quiz1.rs" }, + { name = "primitive_types1", path = "exercises/04_primitive_types/primitive_types1.rs" }, + { name = "primitive_types1_sol", path = "solutions/04_primitive_types/primitive_types1.rs" }, + { name = "primitive_types2", path = "exercises/04_primitive_types/primitive_types2.rs" }, + { name = "primitive_types2_sol", path = "solutions/04_primitive_types/primitive_types2.rs" }, + { name = "primitive_types3", path = "exercises/04_primitive_types/primitive_types3.rs" }, + { name = "primitive_types3_sol", path = "solutions/04_primitive_types/primitive_types3.rs" }, + { name = "primitive_types4", path = "exercises/04_primitive_types/primitive_types4.rs" }, + { name = "primitive_types4_sol", path = "solutions/04_primitive_types/primitive_types4.rs" }, + { name = "primitive_types5", path = "exercises/04_primitive_types/primitive_types5.rs" }, + { name = "primitive_types5_sol", path = "solutions/04_primitive_types/primitive_types5.rs" }, + { name = "primitive_types6", path = "exercises/04_primitive_types/primitive_types6.rs" }, + { name = "primitive_types6_sol", path = "solutions/04_primitive_types/primitive_types6.rs" }, + { name = "vecs1", path = "exercises/05_vecs/vecs1.rs" }, + { name = "vecs1_sol", path = "solutions/05_vecs/vecs1.rs" }, + { name = "vecs2", path = "exercises/05_vecs/vecs2.rs" }, + { name = "vecs2_sol", path = "solutions/05_vecs/vecs2.rs" }, + { name = "move_semantics1", path = "exercises/06_move_semantics/move_semantics1.rs" }, + { name = "move_semantics1_sol", path = "solutions/06_move_semantics/move_semantics1.rs" }, + { name = "move_semantics2", path = "exercises/06_move_semantics/move_semantics2.rs" }, + { name = "move_semantics2_sol", path = "solutions/06_move_semantics/move_semantics2.rs" }, + { name = "move_semantics3", path = "exercises/06_move_semantics/move_semantics3.rs" }, + { name = "move_semantics3_sol", path = "solutions/06_move_semantics/move_semantics3.rs" }, + { name = "move_semantics4", path = "exercises/06_move_semantics/move_semantics4.rs" }, + { name = "move_semantics4_sol", path = "solutions/06_move_semantics/move_semantics4.rs" }, + { name = "move_semantics5", path = "exercises/06_move_semantics/move_semantics5.rs" }, + { name = "move_semantics5_sol", path = "solutions/06_move_semantics/move_semantics5.rs" }, + { name = "structs1", path = "exercises/07_structs/structs1.rs" }, + { name = "structs1_sol", path = "solutions/07_structs/structs1.rs" }, + { name = "structs2", path = "exercises/07_structs/structs2.rs" }, + { name = "structs2_sol", path = "solutions/07_structs/structs2.rs" }, + { name = "structs3", path = "exercises/07_structs/structs3.rs" }, + { name = "structs3_sol", path = "solutions/07_structs/structs3.rs" }, + { name = "enums1", path = "exercises/08_enums/enums1.rs" }, + { name = "enums1_sol", path = "solutions/08_enums/enums1.rs" }, + { name = "enums2", path = "exercises/08_enums/enums2.rs" }, + { name = "enums2_sol", path = "solutions/08_enums/enums2.rs" }, + { name = "enums3", path = "exercises/08_enums/enums3.rs" }, + { name = "enums3_sol", path = "solutions/08_enums/enums3.rs" }, + { name = "strings1", path = "exercises/09_strings/strings1.rs" }, + { name = "strings1_sol", path = "solutions/09_strings/strings1.rs" }, + { name = "strings2", path = "exercises/09_strings/strings2.rs" }, + { name = "strings2_sol", path = "solutions/09_strings/strings2.rs" }, + { name = "strings3", path = "exercises/09_strings/strings3.rs" }, + { name = "strings3_sol", path = "solutions/09_strings/strings3.rs" }, + { name = "strings4", path = "exercises/09_strings/strings4.rs" }, + { name = "strings4_sol", path = "solutions/09_strings/strings4.rs" }, + { name = "modules1", path = "exercises/10_modules/modules1.rs" }, + { name = "modules1_sol", path = "solutions/10_modules/modules1.rs" }, + { name = "modules2", path = "exercises/10_modules/modules2.rs" }, + { name = "modules2_sol", path = "solutions/10_modules/modules2.rs" }, + { name = "modules3", path = "exercises/10_modules/modules3.rs" }, + { name = "modules3_sol", path = "solutions/10_modules/modules3.rs" }, + { name = "hashmaps1", path = "exercises/11_hashmaps/hashmaps1.rs" }, + { name = "hashmaps1_sol", path = "solutions/11_hashmaps/hashmaps1.rs" }, + { name = "hashmaps2", path = "exercises/11_hashmaps/hashmaps2.rs" }, + { name = "hashmaps2_sol", path = "solutions/11_hashmaps/hashmaps2.rs" }, + { name = "hashmaps3", path = "exercises/11_hashmaps/hashmaps3.rs" }, + { name = "hashmaps3_sol", path = "solutions/11_hashmaps/hashmaps3.rs" }, + { name = "quiz2", path = "exercises/quizzes/quiz2.rs" }, + { name = "quiz2_sol", path = "solutions/quizzes/quiz2.rs" }, + { name = "options1", path = "exercises/12_options/options1.rs" }, + { name = "options1_sol", path = "solutions/12_options/options1.rs" }, + { name = "options2", path = "exercises/12_options/options2.rs" }, + { name = "options2_sol", path = "solutions/12_options/options2.rs" }, + { name = "options3", path = "exercises/12_options/options3.rs" }, + { name = "options3_sol", path = "solutions/12_options/options3.rs" }, + { name = "errors1", path = "exercises/13_error_handling/errors1.rs" }, + { name = "errors1_sol", path = "solutions/13_error_handling/errors1.rs" }, + { name = "errors2", path = "exercises/13_error_handling/errors2.rs" }, + { name = "errors2_sol", path = "solutions/13_error_handling/errors2.rs" }, + { name = "errors3", path = "exercises/13_error_handling/errors3.rs" }, + { name = "errors3_sol", path = "solutions/13_error_handling/errors3.rs" }, + { name = "errors4", path = "exercises/13_error_handling/errors4.rs" }, + { name = "errors4_sol", path = "solutions/13_error_handling/errors4.rs" }, + { name = "errors5", path = "exercises/13_error_handling/errors5.rs" }, + { name = "errors5_sol", path = "solutions/13_error_handling/errors5.rs" }, + { name = "errors6", path = "exercises/13_error_handling/errors6.rs" }, + { name = "errors6_sol", path = "solutions/13_error_handling/errors6.rs" }, + { name = "generics1", path = "exercises/14_generics/generics1.rs" }, + { name = "generics1_sol", path = "solutions/14_generics/generics1.rs" }, + { name = "generics2", path = "exercises/14_generics/generics2.rs" }, + { name = "generics2_sol", path = "solutions/14_generics/generics2.rs" }, + { name = "traits1", path = "exercises/15_traits/traits1.rs" }, + { name = "traits1_sol", path = "solutions/15_traits/traits1.rs" }, + { name = "traits2", path = "exercises/15_traits/traits2.rs" }, + { name = "traits2_sol", path = "solutions/15_traits/traits2.rs" }, + { name = "traits3", path = "exercises/15_traits/traits3.rs" }, + { name = "traits3_sol", path = "solutions/15_traits/traits3.rs" }, + { name = "traits4", path = "exercises/15_traits/traits4.rs" }, + { name = "traits4_sol", path = "solutions/15_traits/traits4.rs" }, + { name = "traits5", path = "exercises/15_traits/traits5.rs" }, + { name = "traits5_sol", path = "solutions/15_traits/traits5.rs" }, + { name = "quiz3", path = "exercises/quizzes/quiz3.rs" }, + { name = "quiz3_sol", path = "solutions/quizzes/quiz3.rs" }, + { name = "lifetimes1", path = "exercises/16_lifetimes/lifetimes1.rs" }, + { name = "lifetimes1_sol", path = "solutions/16_lifetimes/lifetimes1.rs" }, + { name = "lifetimes2", path = "exercises/16_lifetimes/lifetimes2.rs" }, + { name = "lifetimes2_sol", path = "solutions/16_lifetimes/lifetimes2.rs" }, + { name = "lifetimes3", path = "exercises/16_lifetimes/lifetimes3.rs" }, + { name = "lifetimes3_sol", path = "solutions/16_lifetimes/lifetimes3.rs" }, + { name = "tests1", path = "exercises/17_tests/tests1.rs" }, + { name = "tests1_sol", path = "solutions/17_tests/tests1.rs" }, + { name = "tests2", path = "exercises/17_tests/tests2.rs" }, + { name = "tests2_sol", path = "solutions/17_tests/tests2.rs" }, + { name = "tests3", path = "exercises/17_tests/tests3.rs" }, + { name = "tests3_sol", path = "solutions/17_tests/tests3.rs" }, + { name = "iterators1", path = "exercises/18_iterators/iterators1.rs" }, + { name = "iterators1_sol", path = "solutions/18_iterators/iterators1.rs" }, + { name = "iterators2", path = "exercises/18_iterators/iterators2.rs" }, + { name = "iterators2_sol", path = "solutions/18_iterators/iterators2.rs" }, + { name = "iterators3", path = "exercises/18_iterators/iterators3.rs" }, + { name = "iterators3_sol", path = "solutions/18_iterators/iterators3.rs" }, + { name = "iterators4", path = "exercises/18_iterators/iterators4.rs" }, + { name = "iterators4_sol", path = "solutions/18_iterators/iterators4.rs" }, + { name = "iterators5", path = "exercises/18_iterators/iterators5.rs" }, + { name = "iterators5_sol", path = "solutions/18_iterators/iterators5.rs" }, + { name = "box1", path = "exercises/19_smart_pointers/box1.rs" }, + { name = "box1_sol", path = "solutions/19_smart_pointers/box1.rs" }, + { name = "rc1", path = "exercises/19_smart_pointers/rc1.rs" }, + { name = "rc1_sol", path = "solutions/19_smart_pointers/rc1.rs" }, + { name = "arc1", path = "exercises/19_smart_pointers/arc1.rs" }, + { name = "arc1_sol", path = "solutions/19_smart_pointers/arc1.rs" }, + { name = "cow1", path = "exercises/19_smart_pointers/cow1.rs" }, + { name = "cow1_sol", path = "solutions/19_smart_pointers/cow1.rs" }, + { name = "threads1", path = "exercises/20_threads/threads1.rs" }, + { name = "threads1_sol", path = "solutions/20_threads/threads1.rs" }, + { name = "threads2", path = "exercises/20_threads/threads2.rs" }, + { 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" }, ] -[workspace.package] -version = "6.4.0" -authors = [ - "Mo Bitar ", # https://github.com/mo8it - "Liv ", # https://github.com/shadows-withal - # Alumni - "Carol (Nichols || Goulding) ", # https://github.com/carols10cents -] -repository = "https://github.com/rust-lang/rustlings" -license = "MIT" -edition = "2024" # On Update: Update the edition of `rustfmt` in `dev check` and `CARGO_TOML` in `dev new`. -rust-version = "1.87" - -[workspace.dependencies] -serde = { version = "1.0", features = ["derive"] } -toml_edit = { version = "0.22", default-features = false, features = ["parse", "serde"] } - [package] -name = "rustlings" -description = "Small exercises to get you used to reading and writing Rust code!" -version.workspace = true -authors.workspace = true -repository.workspace = true -license.workspace = true -edition.workspace = true -rust-version.workspace = true -keywords = [ - "exercise", - "learning", -] -include = [ - "/src/", - "/exercises/", - "/solutions/", - # A symlink to be able to include `dev/Cargo.toml` although `dev` is excluded. - "/dev-Cargo.toml", - "/README.md", - "/LICENSE", -] - -[dependencies] -anyhow = "1.0" -clap = { version = "4.5", features = ["derive"] } -crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] } -notify = "8.0" -rustlings-macros = { path = "rustlings-macros", version = "=6.4.0" } -serde_json = "1.0" -serde.workspace = true -toml_edit.workspace = true - -[target.'cfg(not(windows))'.dependencies] -rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] } - -[dev-dependencies] -tempfile = "3.19" +name = "exercises" +edition = "2021" +# Don't publish the exercises on crates.io! +publish = false [profile.release] panic = "abort" @@ -67,22 +201,22 @@ panic = "abort" [profile.dev] panic = "abort" -[package.metadata.release] -pre-release-hook = ["./release-hook.sh"] -pre-release-commit-message = "Release 🎉" - -[workspace.lints.rust] +[lints.rust] +# You shouldn't write unsafe code in Rustlings! unsafe_code = "forbid" +# You don't need unstable features in Rustlings and shouldn't rely on them while learning Rust. unstable_features = "forbid" +# Dead code warnings can't be avoided in some exercises and might distract while learning. +dead_code = "allow" -[workspace.lints.clippy] +[lints.clippy] +# You forgot a `todo!()`! +todo = "forbid" +# This can only happen by mistake in Rustlings. empty_loop = "forbid" -disallowed-types = "deny" -disallowed-methods = "deny" +# No infinite loops are needed in Rustlings. infinite_loop = "deny" +# You shouldn't leak memory while still learning Rust! mem_forget = "deny" -dbg_macro = "warn" -todo = "warn" - -[lints] -workspace = true +# Currently, there are no disallowed methods. This line avoids problems when developing Rustlings. +disallowed_methods = "allow" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 30f5c61978..0000000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Carol (Nichols || Goulding) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/README.md b/README.md index 0ae6265de9..d227af2935 100644 --- a/README.md +++ b/README.md @@ -1,7 +1 @@ -# [Rustlings](https://rustlings.rust-lang.org) 🦀 - -Small exercises to get you used to reading and writing [Rust](https://www.rust-lang.org) code - _Recommended in parallel to reading [the official Rust book](https://doc.rust-lang.org/book) 📚️_ - -Visit the **website** for a demo, info about setup and more: - -## ➡️ [rustlings.rust-lang.org](https://rustlings.rust-lang.org) ⬅️ +# rustlings diff --git a/build.rs b/build.rs deleted file mode 100644 index 5687864183..0000000000 --- a/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - // Fix building from source on Windows because it can't handle file links. - #[cfg(windows)] - let _ = std::fs::copy("dev/Cargo.toml", "dev-Cargo.toml"); -} diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index afc9253a51..0000000000 --- a/clippy.toml +++ /dev/null @@ -1,15 +0,0 @@ -disallowed-types = [ - # Inefficient. Use `.queue(…)` instead. - "crossterm::style::Stylize", - "crossterm::style::styled_content::StyledContent", -] - -disallowed-methods = [ - # Inefficient. Use `.queue(…)` instead. - "crossterm::style::style", - # Use `thread::Builder::spawn` instead and handle the error. - "std::thread::spawn", - "std::thread::Scope::spawn", - # Return `ExitCode` instead. - "std::process::exit", -] diff --git a/dev-Cargo.toml b/dev-Cargo.toml deleted file mode 120000 index 9230c2e948..0000000000 --- a/dev-Cargo.toml +++ /dev/null @@ -1 +0,0 @@ -dev/Cargo.toml \ No newline at end of file diff --git a/dev/Cargo.toml b/dev/Cargo.toml deleted file mode 100644 index 4f725b704b..0000000000 --- a/dev/Cargo.toml +++ /dev/null @@ -1,223 +0,0 @@ -# Don't edit the `bin` list manually! It is updated by `cargo dev update`. This comment line will be stripped in `rustlings init`. -bin = [ - { name = "intro1", path = "../exercises/00_intro/intro1.rs" }, - { name = "intro1_sol", path = "../solutions/00_intro/intro1.rs" }, - { name = "intro2", path = "../exercises/00_intro/intro2.rs" }, - { name = "intro2_sol", path = "../solutions/00_intro/intro2.rs" }, - { name = "variables1", path = "../exercises/01_variables/variables1.rs" }, - { name = "variables1_sol", path = "../solutions/01_variables/variables1.rs" }, - { name = "variables2", path = "../exercises/01_variables/variables2.rs" }, - { name = "variables2_sol", path = "../solutions/01_variables/variables2.rs" }, - { name = "variables3", path = "../exercises/01_variables/variables3.rs" }, - { name = "variables3_sol", path = "../solutions/01_variables/variables3.rs" }, - { name = "variables4", path = "../exercises/01_variables/variables4.rs" }, - { name = "variables4_sol", path = "../solutions/01_variables/variables4.rs" }, - { name = "variables5", path = "../exercises/01_variables/variables5.rs" }, - { name = "variables5_sol", path = "../solutions/01_variables/variables5.rs" }, - { name = "variables6", path = "../exercises/01_variables/variables6.rs" }, - { name = "variables6_sol", path = "../solutions/01_variables/variables6.rs" }, - { name = "functions1", path = "../exercises/02_functions/functions1.rs" }, - { name = "functions1_sol", path = "../solutions/02_functions/functions1.rs" }, - { name = "functions2", path = "../exercises/02_functions/functions2.rs" }, - { name = "functions2_sol", path = "../solutions/02_functions/functions2.rs" }, - { name = "functions3", path = "../exercises/02_functions/functions3.rs" }, - { name = "functions3_sol", path = "../solutions/02_functions/functions3.rs" }, - { name = "functions4", path = "../exercises/02_functions/functions4.rs" }, - { name = "functions4_sol", path = "../solutions/02_functions/functions4.rs" }, - { name = "functions5", path = "../exercises/02_functions/functions5.rs" }, - { name = "functions5_sol", path = "../solutions/02_functions/functions5.rs" }, - { name = "if1", path = "../exercises/03_if/if1.rs" }, - { name = "if1_sol", path = "../solutions/03_if/if1.rs" }, - { name = "if2", path = "../exercises/03_if/if2.rs" }, - { name = "if2_sol", path = "../solutions/03_if/if2.rs" }, - { name = "if3", path = "../exercises/03_if/if3.rs" }, - { name = "if3_sol", path = "../solutions/03_if/if3.rs" }, - { name = "quiz1", path = "../exercises/quizzes/quiz1.rs" }, - { name = "quiz1_sol", path = "../solutions/quizzes/quiz1.rs" }, - { name = "primitive_types1", path = "../exercises/04_primitive_types/primitive_types1.rs" }, - { name = "primitive_types1_sol", path = "../solutions/04_primitive_types/primitive_types1.rs" }, - { name = "primitive_types2", path = "../exercises/04_primitive_types/primitive_types2.rs" }, - { name = "primitive_types2_sol", path = "../solutions/04_primitive_types/primitive_types2.rs" }, - { name = "primitive_types3", path = "../exercises/04_primitive_types/primitive_types3.rs" }, - { name = "primitive_types3_sol", path = "../solutions/04_primitive_types/primitive_types3.rs" }, - { name = "primitive_types4", path = "../exercises/04_primitive_types/primitive_types4.rs" }, - { name = "primitive_types4_sol", path = "../solutions/04_primitive_types/primitive_types4.rs" }, - { name = "primitive_types5", path = "../exercises/04_primitive_types/primitive_types5.rs" }, - { name = "primitive_types5_sol", path = "../solutions/04_primitive_types/primitive_types5.rs" }, - { name = "primitive_types6", path = "../exercises/04_primitive_types/primitive_types6.rs" }, - { name = "primitive_types6_sol", path = "../solutions/04_primitive_types/primitive_types6.rs" }, - { name = "vecs1", path = "../exercises/05_vecs/vecs1.rs" }, - { name = "vecs1_sol", path = "../solutions/05_vecs/vecs1.rs" }, - { name = "vecs2", path = "../exercises/05_vecs/vecs2.rs" }, - { name = "vecs2_sol", path = "../solutions/05_vecs/vecs2.rs" }, - { name = "move_semantics1", path = "../exercises/06_move_semantics/move_semantics1.rs" }, - { name = "move_semantics1_sol", path = "../solutions/06_move_semantics/move_semantics1.rs" }, - { name = "move_semantics2", path = "../exercises/06_move_semantics/move_semantics2.rs" }, - { name = "move_semantics2_sol", path = "../solutions/06_move_semantics/move_semantics2.rs" }, - { name = "move_semantics3", path = "../exercises/06_move_semantics/move_semantics3.rs" }, - { name = "move_semantics3_sol", path = "../solutions/06_move_semantics/move_semantics3.rs" }, - { name = "move_semantics4", path = "../exercises/06_move_semantics/move_semantics4.rs" }, - { name = "move_semantics4_sol", path = "../solutions/06_move_semantics/move_semantics4.rs" }, - { name = "move_semantics5", path = "../exercises/06_move_semantics/move_semantics5.rs" }, - { name = "move_semantics5_sol", path = "../solutions/06_move_semantics/move_semantics5.rs" }, - { name = "structs1", path = "../exercises/07_structs/structs1.rs" }, - { name = "structs1_sol", path = "../solutions/07_structs/structs1.rs" }, - { name = "structs2", path = "../exercises/07_structs/structs2.rs" }, - { name = "structs2_sol", path = "../solutions/07_structs/structs2.rs" }, - { name = "structs3", path = "../exercises/07_structs/structs3.rs" }, - { name = "structs3_sol", path = "../solutions/07_structs/structs3.rs" }, - { name = "enums1", path = "../exercises/08_enums/enums1.rs" }, - { name = "enums1_sol", path = "../solutions/08_enums/enums1.rs" }, - { name = "enums2", path = "../exercises/08_enums/enums2.rs" }, - { name = "enums2_sol", path = "../solutions/08_enums/enums2.rs" }, - { name = "enums3", path = "../exercises/08_enums/enums3.rs" }, - { name = "enums3_sol", path = "../solutions/08_enums/enums3.rs" }, - { name = "strings1", path = "../exercises/09_strings/strings1.rs" }, - { name = "strings1_sol", path = "../solutions/09_strings/strings1.rs" }, - { name = "strings2", path = "../exercises/09_strings/strings2.rs" }, - { name = "strings2_sol", path = "../solutions/09_strings/strings2.rs" }, - { name = "strings3", path = "../exercises/09_strings/strings3.rs" }, - { name = "strings3_sol", path = "../solutions/09_strings/strings3.rs" }, - { name = "strings4", path = "../exercises/09_strings/strings4.rs" }, - { name = "strings4_sol", path = "../solutions/09_strings/strings4.rs" }, - { name = "modules1", path = "../exercises/10_modules/modules1.rs" }, - { name = "modules1_sol", path = "../solutions/10_modules/modules1.rs" }, - { name = "modules2", path = "../exercises/10_modules/modules2.rs" }, - { name = "modules2_sol", path = "../solutions/10_modules/modules2.rs" }, - { name = "modules3", path = "../exercises/10_modules/modules3.rs" }, - { name = "modules3_sol", path = "../solutions/10_modules/modules3.rs" }, - { name = "hashmaps1", path = "../exercises/11_hashmaps/hashmaps1.rs" }, - { name = "hashmaps1_sol", path = "../solutions/11_hashmaps/hashmaps1.rs" }, - { name = "hashmaps2", path = "../exercises/11_hashmaps/hashmaps2.rs" }, - { name = "hashmaps2_sol", path = "../solutions/11_hashmaps/hashmaps2.rs" }, - { name = "hashmaps3", path = "../exercises/11_hashmaps/hashmaps3.rs" }, - { name = "hashmaps3_sol", path = "../solutions/11_hashmaps/hashmaps3.rs" }, - { name = "quiz2", path = "../exercises/quizzes/quiz2.rs" }, - { name = "quiz2_sol", path = "../solutions/quizzes/quiz2.rs" }, - { name = "options1", path = "../exercises/12_options/options1.rs" }, - { name = "options1_sol", path = "../solutions/12_options/options1.rs" }, - { name = "options2", path = "../exercises/12_options/options2.rs" }, - { name = "options2_sol", path = "../solutions/12_options/options2.rs" }, - { name = "options3", path = "../exercises/12_options/options3.rs" }, - { name = "options3_sol", path = "../solutions/12_options/options3.rs" }, - { name = "errors1", path = "../exercises/13_error_handling/errors1.rs" }, - { name = "errors1_sol", path = "../solutions/13_error_handling/errors1.rs" }, - { name = "errors2", path = "../exercises/13_error_handling/errors2.rs" }, - { name = "errors2_sol", path = "../solutions/13_error_handling/errors2.rs" }, - { name = "errors3", path = "../exercises/13_error_handling/errors3.rs" }, - { name = "errors3_sol", path = "../solutions/13_error_handling/errors3.rs" }, - { name = "errors4", path = "../exercises/13_error_handling/errors4.rs" }, - { name = "errors4_sol", path = "../solutions/13_error_handling/errors4.rs" }, - { name = "errors5", path = "../exercises/13_error_handling/errors5.rs" }, - { name = "errors5_sol", path = "../solutions/13_error_handling/errors5.rs" }, - { name = "errors6", path = "../exercises/13_error_handling/errors6.rs" }, - { name = "errors6_sol", path = "../solutions/13_error_handling/errors6.rs" }, - { name = "generics1", path = "../exercises/14_generics/generics1.rs" }, - { name = "generics1_sol", path = "../solutions/14_generics/generics1.rs" }, - { name = "generics2", path = "../exercises/14_generics/generics2.rs" }, - { name = "generics2_sol", path = "../solutions/14_generics/generics2.rs" }, - { name = "traits1", path = "../exercises/15_traits/traits1.rs" }, - { name = "traits1_sol", path = "../solutions/15_traits/traits1.rs" }, - { name = "traits2", path = "../exercises/15_traits/traits2.rs" }, - { name = "traits2_sol", path = "../solutions/15_traits/traits2.rs" }, - { name = "traits3", path = "../exercises/15_traits/traits3.rs" }, - { name = "traits3_sol", path = "../solutions/15_traits/traits3.rs" }, - { name = "traits4", path = "../exercises/15_traits/traits4.rs" }, - { name = "traits4_sol", path = "../solutions/15_traits/traits4.rs" }, - { name = "traits5", path = "../exercises/15_traits/traits5.rs" }, - { name = "traits5_sol", path = "../solutions/15_traits/traits5.rs" }, - { name = "quiz3", path = "../exercises/quizzes/quiz3.rs" }, - { name = "quiz3_sol", path = "../solutions/quizzes/quiz3.rs" }, - { name = "lifetimes1", path = "../exercises/16_lifetimes/lifetimes1.rs" }, - { name = "lifetimes1_sol", path = "../solutions/16_lifetimes/lifetimes1.rs" }, - { name = "lifetimes2", path = "../exercises/16_lifetimes/lifetimes2.rs" }, - { name = "lifetimes2_sol", path = "../solutions/16_lifetimes/lifetimes2.rs" }, - { name = "lifetimes3", path = "../exercises/16_lifetimes/lifetimes3.rs" }, - { name = "lifetimes3_sol", path = "../solutions/16_lifetimes/lifetimes3.rs" }, - { name = "tests1", path = "../exercises/17_tests/tests1.rs" }, - { name = "tests1_sol", path = "../solutions/17_tests/tests1.rs" }, - { name = "tests2", path = "../exercises/17_tests/tests2.rs" }, - { name = "tests2_sol", path = "../solutions/17_tests/tests2.rs" }, - { name = "tests3", path = "../exercises/17_tests/tests3.rs" }, - { name = "tests3_sol", path = "../solutions/17_tests/tests3.rs" }, - { name = "iterators1", path = "../exercises/18_iterators/iterators1.rs" }, - { name = "iterators1_sol", path = "../solutions/18_iterators/iterators1.rs" }, - { name = "iterators2", path = "../exercises/18_iterators/iterators2.rs" }, - { name = "iterators2_sol", path = "../solutions/18_iterators/iterators2.rs" }, - { name = "iterators3", path = "../exercises/18_iterators/iterators3.rs" }, - { name = "iterators3_sol", path = "../solutions/18_iterators/iterators3.rs" }, - { name = "iterators4", path = "../exercises/18_iterators/iterators4.rs" }, - { name = "iterators4_sol", path = "../solutions/18_iterators/iterators4.rs" }, - { name = "iterators5", path = "../exercises/18_iterators/iterators5.rs" }, - { name = "iterators5_sol", path = "../solutions/18_iterators/iterators5.rs" }, - { name = "box1", path = "../exercises/19_smart_pointers/box1.rs" }, - { name = "box1_sol", path = "../solutions/19_smart_pointers/box1.rs" }, - { name = "rc1", path = "../exercises/19_smart_pointers/rc1.rs" }, - { name = "rc1_sol", path = "../solutions/19_smart_pointers/rc1.rs" }, - { name = "arc1", path = "../exercises/19_smart_pointers/arc1.rs" }, - { name = "arc1_sol", path = "../solutions/19_smart_pointers/arc1.rs" }, - { name = "cow1", path = "../exercises/19_smart_pointers/cow1.rs" }, - { name = "cow1_sol", path = "../solutions/19_smart_pointers/cow1.rs" }, - { name = "threads1", path = "../exercises/20_threads/threads1.rs" }, - { name = "threads1_sol", path = "../solutions/20_threads/threads1.rs" }, - { name = "threads2", path = "../exercises/20_threads/threads2.rs" }, - { 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" }, -] - -[package] -name = "exercises" -edition = "2024" -# Don't publish the exercises on crates.io! -publish = false - -[profile.release] -panic = "abort" - -[profile.dev] -panic = "abort" - -[lints.rust] -# You shouldn't write unsafe code in Rustlings! -unsafe_code = "forbid" -# You don't need unstable features in Rustlings and shouldn't rely on them while learning Rust. -unstable_features = "forbid" -# Dead code warnings can't be avoided in some exercises and might distract while learning. -dead_code = "allow" - -[lints.clippy] -# You forgot a `todo!()`! -todo = "forbid" -# This can only happen by mistake in Rustlings. -empty_loop = "forbid" -# No infinite loops are needed in Rustlings. -infinite_loop = "deny" -# You shouldn't leak memory while still learning Rust! -mem_forget = "deny" -# Currently, there are no disallowed methods. This line avoids problems when developing Rustlings. -disallowed_methods = "allow" diff --git a/dev/rustlings-repo.txt b/dev/rustlings-repo.txt deleted file mode 100644 index 5d456c8803..0000000000 --- a/dev/rustlings-repo.txt +++ /dev/null @@ -1 +0,0 @@ -This file is used to check if the user tries to run Rustlings in the repository (the method before version 6) diff --git a/release-hook.sh b/release-hook.sh deleted file mode 100755 index 42135369ab..0000000000 --- a/release-hook.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# Error out if any command fails -set -e - -typos -cargo upgrades - -# Similar to CI -cargo clippy -- --deny warnings -cargo fmt --all --check -cargo test --workspace -cargo dev check --require-solutions - -# MSRV -cargo +1.87 dev check --require-solutions diff --git a/rust-analyzer.toml b/rust-analyzer.toml new file mode 100644 index 0000000000..e626ed53f6 --- /dev/null +++ b/rust-analyzer.toml @@ -0,0 +1,2 @@ +check.command = "clippy" +check.extraArgs = ["--profile", "test"] diff --git a/rustlings-macros/Cargo.toml b/rustlings-macros/Cargo.toml deleted file mode 100644 index 1bf6d1b8c7..0000000000 --- a/rustlings-macros/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "rustlings-macros" -description = "A macros crate intended to be used only by Rustlings" -version.workspace = true -authors.workspace = true -repository.workspace = true -license.workspace = true -edition.workspace = true -rust-version.workspace = true -include = [ - "/src/", - "/info.toml", -] - -[lib] -proc-macro = true - -[dependencies] -quote = "1.0" -serde.workspace = true -toml_edit.workspace = true - -[lints] -workspace = true diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml deleted file mode 100644 index 516fd321fe..0000000000 --- a/rustlings-macros/info.toml +++ /dev/null @@ -1,1211 +0,0 @@ -format_version = 1 - -welcome_message = """ -Is this your first time? Don't worry, Rustlings is made for beginners! -We are going to teach you a lot of things about Rust, but before we can -get started, here are some notes about how Rustlings operates: - -1. The central concept behind Rustlings is that you solve exercises. These - exercises usually contain some compiler or logic errors which cause the - exercise to fail compilation or testing. It's your job to find all errors - and fix them! -2. Make sure to have your editor open in the `rustlings/` directory. Rustlings - will show you the path of the current exercise under the progress bar. Open - the exercise file in your editor, fix errors and save the file. Rustlings - will automatically detect the file change and rerun the exercise. If all - errors are fixed, Rustlings will ask you to move on to the next exercise. -3. If you're stuck on an exercise, enter `h` to show a hint. -4. If an exercise doesn't make sense to you, feel free to open an issue on - GitHub! (https://github.com/rust-lang/rustlings). We look at every issue, and - sometimes, other learners do too so you can help each other out!""" - -final_message = """ -We hope you enjoyed learning about the various aspects of Rust! -If you noticed any issues, don't hesitate to report them on Github. -You can also contribute your own exercises to help the greater community! - -Before reporting an issue or contributing, please read our guidelines: -https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md""" - -# INTRO - -[[exercises]] -name = "intro1" -dir = "00_intro" -test = false -skip_check_unsolved = true -hint = """ -Enter `n` to move on to the next exercise. -You might need to press ENTER after typing `n`.""" - -[[exercises]] -name = "intro2" -dir = "00_intro" -test = false -hint = """ -The compiler is informing us that we've got the name of the print macro wrong. -It also suggests an alternative.""" - -# VARIABLES - -[[exercises]] -name = "variables1" -dir = "01_variables" -test = false -hint = """ -The declaration in the `main` function is missing a keyword that is needed -in Rust to create a new variable binding.""" - -[[exercises]] -name = "variables2" -dir = "01_variables" -test = false -hint = """ -The compiler message is saying that Rust can't infer the type that the -variable binding `x` has with what is given here. - -What happens if you annotate the first line in the `main` function with a type -annotation? - -What if you give `x` a value? - -What if you do both? - -What type should `x` be, anyway? - -What if `x` is the same type as `10`? What if it's a different type?""" - -[[exercises]] -name = "variables3" -dir = "01_variables" -test = false -hint = """ -In this exercise, we have a variable binding that we've created in the `main` -function, and we're trying to use it in the next line, but we haven't given it -a value. - -We can't print out something that isn't there; try giving `x` a value! - -This is an error that can cause bugs that's very easy to make in any -programming language -- thankfully the Rust compiler has caught this for us!""" - -[[exercises]] -name = "variables4" -dir = "01_variables" -test = false -hint = """ -In Rust, variable bindings are immutable by default. But here, we're trying -to reassign a different value to `x`! There's a keyword we can use to make -a variable binding mutable instead.""" - -[[exercises]] -name = "variables5" -dir = "01_variables" -test = false -hint = """ -In `variables4` we already learned how to make an immutable variable mutable -using a special keyword. Unfortunately this doesn't help us much in this -exercise because we want to assign a different typed value to an existing -variable. Sometimes you may also like to reuse existing variable names because -you are just converting values to different types like in this exercise. - -Fortunately Rust has a powerful solution to this problem: 'Shadowing'! -You can read more about 'Shadowing' in the book's section 'Variables and -Mutability': -https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing - -Try to solve this exercise afterwards using this technique.""" - -[[exercises]] -name = "variables6" -dir = "01_variables" -test = false -hint = """ -We know about variables and mutability, but there is another important type of -variable available: constants. - -Constants are always immutable. They are declared with the keyword `const` -instead of `let`. - -The type of Constants must always be annotated. - -Read more about constants and the differences between variables and constants -under 'Constants' in the book's section 'Variables and Mutability': -https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#constants""" - -# FUNCTIONS - -[[exercises]] -name = "functions1" -dir = "02_functions" -test = false -hint = """ -This `main` function is calling a function that it expects to exist, but the -function doesn't exist. It expects this function to have the name `call_me`. -It also expects this function to not take any arguments and not return a value. -Sounds a lot like `main`, doesn't it?""" - -[[exercises]] -name = "functions2" -dir = "02_functions" -test = false -hint = """ -Rust requires that all parts of a function's signature have type annotations, -but `call_me` is missing the type annotation of `num`.""" - -[[exercises]] -name = "functions3" -dir = "02_functions" -test = false -hint = """ -This time, the function *declaration* is okay, but there's something wrong -with the place where we are calling the function.""" - -[[exercises]] -name = "functions4" -dir = "02_functions" -test = false -hint = """ -The error message points to the function `sale_price` and says it expects a type -after `->`. This is where the function's return type should be. -Take a look at the `is_even` function for an example!""" - -[[exercises]] -name = "functions5" -dir = "02_functions" -test = false -hint = """ -This is a really common error that can be fixed by removing one character. -It happens because Rust distinguishes between expressions and statements: -Expressions return a value based on their operand(s), and statements simply -return a `()` type which behaves just like `void` in C/C++. - -We want to return a value with the type `i32` from the `square` function, but -it is returning the type `()`. - -There are two solutions: -1. Add the `return` keyword before `num * num;` -2. Remove the semicolon `;` after `num * num`""" - -# IF - -[[exercises]] -name = "if1" -dir = "03_if" -hint = """ -It's possible to do this in one line if you would like! - -Some similar examples from other languages: -- In C(++) this would be: `a > b ? a : b` -- In Python this would be: `a if a > b else b` - -Remember in Rust that: -- The `if` condition does not need to be surrounded by parentheses -- `if`/`else` conditionals are expressions -- Each condition is followed by a `{}` block""" - -[[exercises]] -name = "if2" -dir = "03_if" -hint = """ -For that first compiler error, it's important in Rust that each conditional -block returns the same type! - -To get the tests passing, you will need a couple conditions checking different -input values. Read the tests to find out what they expect.""" - -[[exercises]] -name = "if3" -dir = "03_if" -hint = """ -In Rust, every arm of an `if` expression has to return the same type of value. -Make sure the type is consistent across all arms.""" - -# QUIZ 1 - -[[exercises]] -name = "quiz1" -dir = "quizzes" -hint = "No hints this time ;)" - -# PRIMITIVE TYPES - -[[exercises]] -name = "primitive_types1" -dir = "04_primitive_types" -test = false -hint = """ -In Rust, a boolean can be negated using the operator `!` before it. -Example: `!true == false` -This also works with boolean variables.""" - -[[exercises]] -name = "primitive_types2" -dir = "04_primitive_types" -test = false -hint = "No hints this time ;)" - -[[exercises]] -name = "primitive_types3" -dir = "04_primitive_types" -test = false -hint = """ -There's a shorthand to initialize arrays with a certain size that doesn't -require you to type in 100 items (but you certainly can if you want!). - -For example, you can do: -``` -let array = ["Are we there yet?"; 100]; -``` - -Bonus: what are some other things you could have that would return `true` -for `a.len() >= 100`?""" - -[[exercises]] -name = "primitive_types4" -dir = "04_primitive_types" -hint = """ -Take a look at the 'Understanding Ownership -> Slices -> Other Slices' section -of the book: https://doc.rust-lang.org/book/ch04-03-slices.html and use the -starting and ending (plus one) indices of the items in the array that you want -to end up in the slice. - -If you're curious why the first argument of `assert_eq!` does not have an -ampersand for a reference since the second argument is a reference, take a look -at the coercion chapter of the nomicon: -https://doc.rust-lang.org/nomicon/coercions.html""" - -[[exercises]] -name = "primitive_types5" -dir = "04_primitive_types" -test = false -hint = """ -Take a look at the 'Data Types -> The Tuple Type' section of the book: -https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type -Particularly the part about destructuring (second to last example in the -section). - -You'll need to make a pattern to bind `name` and `age` to the appropriate parts -of the tuple.""" - -[[exercises]] -name = "primitive_types6" -dir = "04_primitive_types" -hint = """ -While you could use a destructuring `let` for the tuple here, try -indexing into it instead, as explained in the last example of the -'Data Types -> The Tuple Type' section of the book: -https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type -Now, you have another tool in your toolbox!""" - -# VECS - -[[exercises]] -name = "vecs1" -dir = "05_vecs" -hint = """ -In Rust, there are two ways to define a Vector. -1. One way is to use the `Vec::new()` function to create a new vector - and fill it with the `push()` method. -2. The second way is to use the `vec![]` macro and define your elements - inside the square brackets. This way is simpler when you exactly know - the initial values. - -Check this chapter: https://doc.rust-lang.org/book/ch08-01-vectors.html -of the Rust book to learn more.""" - -[[exercises]] -name = "vecs2" -dir = "05_vecs" -hint = """ -In the first function, we create an empty vector and want to push new elements -to it. - -In the second function, we map the values of the input and collect them into -a vector. - -After you've completed both functions, decide for yourself which approach you -like better. - -What do you think is the more commonly used pattern under Rust developers?""" - -# MOVE SEMANTICS - -[[exercises]] -name = "move_semantics1" -dir = "06_move_semantics" -hint = """ -So you've got the "cannot borrow `vec` as mutable, as it is not declared as -mutable" error on the line where we push an element to the vector, right? - -The fix for this is going to be adding one keyword, and the addition is NOT on -the line where we push to the vector (where the error is). - -Try accessing `vec0` after having called `fill_vec()`. See what happens!""" - -[[exercises]] -name = "move_semantics2" -dir = "06_move_semantics" -hint = """ -When running this exercise for the first time, you'll notice an error about -"borrow of moved value". In Rust, when an argument is passed to a function and -it's not explicitly returned, you can't use the original variable anymore. -We call this "moving" a variable. When we pass `vec0` into `fill_vec`, it's -being "moved" into `vec1`, meaning we can't access `vec0` anymore. - -You could make another, separate version of the data that's in `vec0` and -pass it to `fill_vec` instead. This is called cloning in Rust.""" - -[[exercises]] -name = "move_semantics3" -dir = "06_move_semantics" -hint = """ -The difference between this one and the previous ones is that the first line -of `fn fill_vec` that had `let mut vec = vec;` is no longer there. You can, -instead of adding that line back, add `mut` in one place that will change -an existing binding to be a mutable binding instead of an immutable one :)""" - -[[exercises]] -name = "move_semantics4" -dir = "06_move_semantics" -hint = """ -Carefully reason about the range in which each mutable reference is in -scope. Does it help to update the value of `x` immediately after -the mutable reference is taken? -Read more about 'Mutable References' in the book's section 'References and -Borrowing': -https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-references.""" - -[[exercises]] -name = "move_semantics5" -dir = "06_move_semantics" -test = false -hint = """ -To find the answer, you can consult the book section "References and Borrowing": -https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html - -The first problem is that `get_char` is taking ownership of the string. So -`data` is moved and can't be used for `string_uppercase`. `data` is moved to -`get_char` first, meaning that `string_uppercase` can't manipulate the data. - -Once you've fixed that, `string_uppercase`'s function signature will also need -to be adjusted.""" - -# STRUCTS - -[[exercises]] -name = "structs1" -dir = "07_structs" -hint = """ -Rust has more than one type of struct. Three actually, all variants are used to -package related data together. - -There are regular structs. These are named collections of related data stored in -fields. - -Tuple structs are basically just named tuples. - -Finally, unit structs. These don't have any fields and are useful for generics. - -In this exercise, you need to complete and implement one of each kind. -Read more about structs in The Book: -https://doc.rust-lang.org/book/ch05-01-defining-structs.html""" - -[[exercises]] -name = "structs2" -dir = "07_structs" -hint = """ -Creating instances of structs is easy, all you need to do is assign some values -to its fields. - -There are however some shortcuts that can be taken when instantiating structs. -Have a look in The Book to find out more: -https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax""" - -[[exercises]] -name = "structs3" -dir = "07_structs" -hint = """ -For `is_international`: What makes a package international? Seems related to -the places it goes through right? - -For `get_fees`: This method takes an additional argument, is there a field in -the `Package` struct that this relates to? - -Have a look in The Book to find out more about method implementations: -https://doc.rust-lang.org/book/ch05-03-method-syntax.html""" - -# ENUMS - -[[exercises]] -name = "enums1" -dir = "08_enums" -test = false -hint = "No hints this time ;)" - -[[exercises]] -name = "enums2" -dir = "08_enums" -test = false -hint = """ -You can create enumerations that have different variants with different types -such as anonymous structs, structs, a single string, tuples, no data, etc.""" - -[[exercises]] -name = "enums3" -dir = "08_enums" -hint = """ -As a first step, define enums to compile the code without errors. - -Then, create a match expression in `process()`. - -Note that you need to deconstruct some message variants in the match expression -to get the variant's values.""" - -# STRINGS - -[[exercises]] -name = "strings1" -dir = "09_strings" -test = false -hint = """ -The `current_favorite_color` function is currently returning a string slice -with the `'static` lifetime. We know this because the data of the string lives -in our code itself -- it doesn't come from a file or user input or another -program -- so it will live as long as our program lives. - -But it is still a string slice. There's one way to create a `String` by -converting a string slice covered in the Strings chapter of the book, and -another way that uses the `From` trait.""" - -[[exercises]] -name = "strings2" -dir = "09_strings" -test = false -hint = """ -Yes, it would be really easy to fix this by just changing the value bound to -`word` to be a string slice instead of a `String`, wouldn't it? There is a way -to add one character to the `if` statement, though, that will coerce the -`String` into a string slice. - -Side note: If you're interested in learning about how this kind of reference -conversion works, you can jump ahead in the book and read this part in the -smart pointers chapter: -https://doc.rust-lang.org/book/ch15-02-deref.html#implicit-deref-coercions-with-functions-and-methods""" - -[[exercises]] -name = "strings3" -dir = "09_strings" -hint = """ -There are many useful standard library functions for strings. Let's try and use -some of them: -https://doc.rust-lang.org/std/string/struct.String.html#method.trim - -For the `compose_me` method: You can either use the `format!` macro, or convert -the string slice into an owned string, which you can then freely extend. - -For the `replace_me` method, you can check out the `replace` method: -https://doc.rust-lang.org/std/string/struct.String.html#method.replace""" - -[[exercises]] -name = "strings4" -dir = "09_strings" -test = false -hint = """ -Replace `placeholder` with either `string` or `string_slice` in the `main` -function. - -Example: -`placeholder("blue");` -should become -`string_slice("blue");` -because "blue" is `&str`, not `String`.""" - -# MODULES - -[[exercises]] -name = "modules1" -dir = "10_modules" -test = false -hint = """ -Everything is private in Rust by default. But there's a keyword we can use -to make something public!""" - -[[exercises]] -name = "modules2" -dir = "10_modules" -test = false -hint = """ -The `delicious_snacks` module is trying to present an external interface that -is different than its internal structure (the `fruits` and `veggies` modules -and associated constants). Complete the `use` statements to fit the uses in -`main` and find the one keyword missing for both constants. - -Learn more in The Book: -https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#re-exporting-names-with-pub-use""" - -[[exercises]] -name = "modules3" -dir = "10_modules" -test = false -hint = """ -`UNIX_EPOCH` and `SystemTime` are declared in the `std::time` module. Add a -`use` statement for these two to bring them into scope. You can use nested -paths to bring these two in using only one line.""" - -# HASHMAPS - -[[exercises]] -name = "hashmaps1" -dir = "11_hashmaps" -hint = """ -The number of fruits should be at least 5 and you have to put at least 3 -different types of fruits.""" - -[[exercises]] -name = "hashmaps2" -dir = "11_hashmaps" -hint = """ -Use the `entry()` and `or_insert()` methods of `HashMap` to achieve this. - -Learn more in The Book: -https://doc.rust-lang.org/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value""" - -[[exercises]] -name = "hashmaps3" -dir = "11_hashmaps" -hint = """ -Hint 1: Use the `entry()` and `or_default()` methods of `HashMap` to insert the - default value of `TeamScores` if a team doesn't exist in the table yet. - -Hint 2: If there is already an entry for a given key, the value returned by - `entry()` can be updated based on the existing value. - -Learn more in The Book: -https://doc.rust-lang.org/book/ch08-03-hash-maps.html#updating-a-value-based-on-the-old-value""" - -# QUIZ 2 - -[[exercises]] -name = "quiz2" -dir = "quizzes" -hint = "The `+` operator can concatenate a `String` with a `&str`." - -# OPTIONS - -[[exercises]] -name = "options1" -dir = "12_options" -hint = """ -Options can have a `Some` value, with an inner value, or a `None` value, -without an inner value. - -There are multiple ways to get at the inner value, you can use `unwrap`, or -pattern match. Unwrapping is the easiest, but how do you do it safely so that -it doesn't panic in your face later?""" - -[[exercises]] -name = "options2" -dir = "12_options" -hint = """ -Check out: - -- https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html -- https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html - -Remember that `Option`s can be nested in if-let and while-let statements. - -For example: `if let Some(Some(x)) = y` - -Also see `Option::flatten`""" - -[[exercises]] -name = "options3" -dir = "12_options" -test = false -hint = """ -The compiler says a partial move happened in the `match` statement. How can -this be avoided? The compiler shows the correction needed. - -After making the correction as suggested by the compiler, read the related docs -page: -https://doc.rust-lang.org/std/keyword.ref.html""" - -# ERROR HANDLING - -[[exercises]] -name = "errors1" -dir = "13_error_handling" -hint = """ -`Ok` and `Err` are the two variants of `Result`, so what the tests are saying -is that `generate_nametag_text` should return a `Result` instead of an `Option`. - -To make this change, you'll need to: - - update the return type in the function signature to be a `Result` that could be the variants `Ok(String)` and `Err(String)` - - change the body of the function to return `Ok(…)` where it currently - returns `Some(…)` - - change the body of the function to return `Err(error message)` where it - currently returns `None`""" - -[[exercises]] -name = "errors2" -dir = "13_error_handling" -hint = """ -One way to handle this is using a `match` statement on -`item_quantity.parse::()` where the cases are `Ok(something)` and -`Err(something)`. - -This pattern is very common in Rust, though, so there's the `?` operator that -does pretty much what you would make that match statement do for you! - -Take a look at this section of the "Error Handling" chapter: -https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator""" - -[[exercises]] -name = "errors3" -dir = "13_error_handling" -test = false -hint = """ -If other functions can return a `Result`, why shouldn't `main`? It's a fairly -common convention to return something like `Result<(), ErrorType>` from your -`main` function. - -The unit type `()` is there because nothing is really needed in terms of a -positive result.""" - -[[exercises]] -name = "errors4" -dir = "13_error_handling" -hint = """ -`PositiveNonzeroInteger::new` is always creating a new instance and returning -an `Ok` result. But it should be doing some checking, returning an `Err` if -those checks fail, and only returning an `Ok` if those checks determine that -everything is… okay :)""" - -[[exercises]] -name = "errors5" -dir = "13_error_handling" -test = false -hint = """ -There are two different possible `Result` types produced within the `main` -function, which are propagated using the `?` operators. How do we declare a -return type for the `main` function that allows both? - -Under the hood, the `?` operator calls `From::from` on the error value to -convert it to a boxed trait object, a `Box`. This boxed trait object -is polymorphic, and since all errors implement the `Error` trait, we can capture -lots of different errors in one `Box` object. - -Check out this section of The Book: -https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator - -Read more about boxing errors: -https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/boxing_errors.html - -Read more about using the `?` operator with boxed errors: -https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html""" - -[[exercises]] -name = "errors6" -dir = "13_error_handling" -hint = """ -This exercise uses a completed version of `PositiveNonzeroInteger` from the -previous exercises. - -Below the line that `TODO` asks you to change, there is an example of using -the `map_err()` method on a `Result` to transform one type of error into -another. Try using something similar on the `Result` from `parse()`. You -can then use the `?` operator to return early. - -Read more about `map_err()` in the `std::result` documentation: -https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err""" - -# Generics - -[[exercises]] -name = "generics1" -dir = "14_generics" -test = false -hint = """ -Vectors in Rust make use of generics to create dynamically sized arrays of any -type. -If the vector `numbers` has the type `Vec`, then we can only push values of -type `T` to it. By using `into()` before pushing, we ask the compiler to convert -`n1` and `n2` to `T`. But the compiler doesn't know what `T` is yet and needs a -type annotation. - -`u8` and `i8` can both be converted to `i16`, `i32` and `i64`. Choose one for -the generic of the vector.""" - -[[exercises]] -name = "generics2" -dir = "14_generics" -hint = """ -Related section in The Book: -https://doc.rust-lang.org/book/ch10-01-syntax.html#in-method-definitions""" - -# TRAITS - -[[exercises]] -name = "traits1" -dir = "15_traits" -hint = """ -More about traits in The Book: -https://doc.rust-lang.org/book/ch10-02-traits.html - -The `+` operator can concatenate a `String` with a `&str`.""" - -[[exercises]] -name = "traits2" -dir = "15_traits" -hint = """ -Notice how the trait takes ownership of `self` and returns `Self`. - -Although the signature of `append_bar` in the trait takes `self` as argument, -the implementation can take `mut self` instead. This is possible because the -value is owned anyway.""" - -[[exercises]] -name = "traits3" -dir = "15_traits" -hint = """ -Traits can have a default implementation for functions. Data types that -implement the trait can then use the default version of these functions -if they choose not to implement the function themselves. - -Related section in The Book: -https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations""" - -[[exercises]] -name = "traits4" -dir = "15_traits" -hint = """ -Instead of using concrete types as parameters you can use traits. Try replacing -`???` with `impl [what goes here?]`. - -Related section in The Book: -https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters""" - -[[exercises]] -name = "traits5" -dir = "15_traits" -hint = """ -To ensure a parameter implements multiple traits use the '+ syntax'. Try -replacing `???` with 'impl [what goes here?] + [what goes here?]'. - -Related section in The Book: -https://doc.rust-lang.org/book/ch10-02-traits.html#specifying-multiple-trait-bounds-with-the--syntax""" - -# QUIZ 3 - -[[exercises]] -name = "quiz3" -dir = "quizzes" -hint = """ -To find the best solution to this challenge, you need to recall your knowledge -of traits, specifically "Trait Bound Syntax": -https://doc.rust-lang.org/book/ch10-02-traits.html#trait-bound-syntax - -Here is how to specify a trait bound for an implementation block: -`impl for Foo { … }` - -You may need this: -`use std::fmt::Display;`""" - -# LIFETIMES - -[[exercises]] -name = "lifetimes1" -dir = "16_lifetimes" -hint = """ -Let the compiler guide you. Also take a look at The Book if you need help: -https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html""" - -[[exercises]] -name = "lifetimes2" -dir = "16_lifetimes" -test = false -hint = """ -Remember that the generic lifetime `'a` will get the concrete lifetime that is -equal to the smaller of the lifetimes of `x` and `y`. - -You can take at least two paths to achieve the desired result while keeping the -inner block: -1. Move the `string2` declaration to make it live as long as `string1` (how is - `result` declared?) -2. Move `println!` into the inner block""" - -[[exercises]] -name = "lifetimes3" -dir = "16_lifetimes" -test = false -hint = """Let the compiler guide you :)""" - -# TESTS - -[[exercises]] -name = "tests1" -dir = "17_tests" -hint = """ -`assert!` is a macro that needs an argument. Depending on the value of the -argument, `assert!` will do nothing (in which case the test will pass) or -`assert!` will panic (in which case the test will fail). - -So try giving different values to `assert!` and see which ones compile, which -ones pass, and which ones fail :) - -If you want to check for `false`, you can negate the result of what you're -checking using `!`, like `assert!(!…)`.""" - -[[exercises]] -name = "tests2" -dir = "17_tests" -hint = """ -`assert_eq!` is a macro that takes two arguments and compares them. Try giving -it two values that are equal! Try giving it two arguments that are different! -Try switching which argument comes first and which comes second!""" - -[[exercises]] -name = "tests3" -dir = "17_tests" -hint = """ -We expect the method `Rectangle::new` to panic for negative values. - -To handle that, you need to add a special attribute to the test function. - -You can refer to the docs: -https://doc.rust-lang.org/book/ch11-01-writing-tests.html#checking-for-panics-with-should_panic""" - -# STANDARD LIBRARY TYPES - -[[exercises]] -name = "iterators1" -dir = "18_iterators" -hint = """ -An iterator goes through all elements in a collection, but what if we've run -out of elements? What should we expect here? If you're stuck, take a look at -https://doc.rust-lang.org/std/iter/trait.Iterator.html""" - -[[exercises]] -name = "iterators2" -dir = "18_iterators" -hint = """ -`capitalize_first`: - -The variable `first` is a `char`. It needs to be capitalized and added to the -remaining characters in `chars` in order to return the correct `String`. - -The remaining characters in `chars` can be viewed as a string slice using the -`as_str` method. - -The documentation for `char` contains many useful methods. -https://doc.rust-lang.org/std/primitive.char.html - -Use `char::to_uppercase`. It returns an iterator that can be converted to a -`String`. - -`capitalize_words_vector`: - -Create an iterator from the slice. Transform the iterated values by applying -the `capitalize_first` function. Remember to `collect` the iterator. - -`capitalize_words_string`: - -This is surprisingly similar to the previous solution. `collect` is very -powerful and very general. Rust just needs to know the desired type.""" - -[[exercises]] -name = "iterators3" -dir = "18_iterators" -hint = """ -The `divide` function needs to return the correct error when the divisor is 0 or -when even division is not possible. - -The `division_results` variable needs to be collected into a collection type. - -The `result_with_list` function needs to return a single `Result` where the -success case is a vector of integers and the failure case is a `DivisionError`. - -The `list_of_results` function needs to return a vector of results. - -See https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect for -how the `FromIterator` trait is used in `collect()`. This trait is REALLY -powerful! It can make the solution to this exercise much easier.""" - -[[exercises]] -name = "iterators4" -dir = "18_iterators" -hint = """ -In an imperative language, you might write a `for` loop that updates a mutable -variable. Or, you might write code utilizing recursion and a match clause. In -Rust, you can take another functional approach, computing the factorial -elegantly with ranges and iterators. - -Check out the `fold` and `rfold` methods!""" - -[[exercises]] -name = "iterators5" -dir = "18_iterators" -hint = """ -The documentation for the `std::iter::Iterator` trait contains numerous methods -that would be helpful here. - -The `collection` variable in `count_collection_iterator` is a slice of -`HashMap`s. It needs to be converted into an iterator in order to use the -iterator methods. - -The `fold` method can be useful in the `count_collection_iterator` function. - -For a further challenge, consult the documentation for `Iterator` to find -a different method that could make your code more compact than using `fold`.""" - -# SMART POINTERS - -[[exercises]] -name = "box1" -dir = "19_smart_pointers" -hint = """ -The compiler's message should help: Since we cannot store the value of the -actual type when working with recursive types, we need to store a reference -(pointer) to its value. - -We should, therefore, place our `List` inside a `Box`. More details in The Book: -https://doc.rust-lang.org/book/ch15-01-box.html#enabling-recursive-types-with-boxes - -Creating an empty list should be fairly straightforward (Hint: Read the tests). - -For a non-empty list, keep in mind that we want to use our `Cons` list builder. -Although the current list is one of integers (`i32`), feel free to change the -definition and try other types!""" - -[[exercises]] -name = "rc1" -dir = "19_smart_pointers" -hint = """ -This is a straightforward exercise to use the `Rc` type. Each `Planet` has -ownership of the `Sun`, and uses `Rc::clone()` to increment the reference count -of the `Sun`. - -After using `drop()` to move the `Planet`s out of scope individually, the -reference count goes down. - -In the end, the `Sun` only has one reference again, to itself. - -See more at: https://doc.rust-lang.org/book/ch15-04-rc.html - -Unfortunately, Pluto is no longer considered a planet :(""" - -[[exercises]] -name = "arc1" -dir = "19_smart_pointers" -test = false -hint = """ -Make `shared_numbers` be an `Arc` from the `numbers` vector. Then, in order -to avoid creating a copy of `numbers`, you'll need to create `child_numbers` -inside the loop but still in the main thread. - -`child_numbers` should be a clone of the `Arc` of the numbers instead of a -thread-local copy of the numbers. - -This is a simple exercise if you understand the underlying concepts, but if this -is too much of a struggle, consider reading through all of Chapter 16 in The -Book: -https://doc.rust-lang.org/book/ch16-00-concurrency.html""" - -[[exercises]] -name = "cow1" -dir = "19_smart_pointers" -hint = """ -If `Cow` already owns the data, it doesn't need to clone it when `to_mut()` is -called. - -Check out the documentation of the `Cow` type: -https://doc.rust-lang.org/std/borrow/enum.Cow.html""" - -# THREADS - -[[exercises]] -name = "threads1" -dir = "20_threads" -test = false -hint = """ -`JoinHandle` is a struct that is returned from a spawned thread: -https://doc.rust-lang.org/std/thread/fn.spawn.html - -A challenge with multi-threaded applications is that the main thread can -finish before the spawned threads are done. -https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles - -Use the `JoinHandle`s to wait for each thread to finish and collect their -results. - -https://doc.rust-lang.org/std/thread/struct.JoinHandle.html""" - -[[exercises]] -name = "threads2" -dir = "20_threads" -test = false -hint = """ -`Arc` is an Atomic Reference Counted pointer that allows safe, shared access -to **immutable** data. But we want to *change* the number of `jobs_done` so -we'll need to also use another type that will only allow one thread to mutate -the data at a time. Take a look at this section of the book: -https://doc.rust-lang.org/book/ch16-03-shared-state.html#atomic-reference-counting-with-arct - -Keep reading if you'd like more hints :) - -Do you now have an `Arc>` at the beginning of `main`? Like: -``` -let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 })); -``` - -Similar to the code in the following example in The Book: -https://doc.rust-lang.org/book/ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads""" - -[[exercises]] -name = "threads3" -dir = "20_threads" -hint = """ -An alternate way to handle concurrency between threads is to use an `mpsc` -(multiple producer, single consumer) channel to communicate. - -With both a sending end and a receiving end, it's possible to send values in -one thread and receive them in another. - -Multiple producers are possible by using `clone()` to create a duplicate of the -original sending end. - -Related section in The Book: -https://doc.rust-lang.org/book/ch16-02-message-passing.html""" - -# MACROS - -[[exercises]] -name = "macros1" -dir = "21_macros" -test = false -hint = """ -When you call a macro, you need to add something special compared to a regular -function call.""" - -[[exercises]] -name = "macros2" -dir = "21_macros" -test = false -hint = """ -Macros don't quite play by the same rules as the rest of Rust, in terms of -what's available where. - -Unlike other things in Rust, the order of "where you define a macro" versus -"where you use it" actually matters.""" - -[[exercises]] -name = "macros3" -dir = "21_macros" -test = false -hint = """ -In order to use a macro outside of its module, you need to do something -special to the module to lift the macro out into its parent.""" - -[[exercises]] -name = "macros4" -dir = "21_macros" -test = false -hint = """ -You only need to add a single character to make this compile. - -The way macros are written, it wants to see something between each "macro arm", -so it can separate them. - -That's all the macro exercises we have in here, but it's barely even scratching -the surface of what you can do with Rust's macros. For a more thorough -introduction, you can have a read through 'The Little Book of Rust Macros': -https://veykril.github.io/tlborm/""" - -# CLIPPY - -[[exercises]] -name = "clippy1" -dir = "22_clippy" -test = false -strict_clippy = true -hint = """ -Rust stores the highest precision version of some long or infinite precision -mathematical constants in the Rust standard library: -https://doc.rust-lang.org/stable/std/f32/consts/index.html - -We may be tempted to use our own approximations for certain mathematical -constants, but clippy recognizes those imprecise mathematical constants as a -source of potential error. - -See the suggestions of the Clippy warning in the compile output and use the -appropriate replacement constant from `std::f32::consts`.""" - -[[exercises]] -name = "clippy2" -dir = "22_clippy" -test = false -strict_clippy = true -hint = """ -`for` loops over `Option` values are more clearly expressed as an `if-let` -statement. - -Not required to solve this exercise, but if you are interested in when iterating -over `Option` can be useful, read the following section in the documentation: -https://doc.rust-lang.org/std/option/#iterating-over-option""" - -[[exercises]] -name = "clippy3" -dir = "22_clippy" -test = false -strict_clippy = true -hint = "No hints this time!" - -# TYPE CONVERSIONS - -[[exercises]] -name = "using_as" -dir = "23_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" -hint = """ -Follow the steps provided right before the `From` implementation.""" - -[[exercises]] -name = "from_str" -dir = "23_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. - -This is almost like the previous `from_into` exercise, but returning errors -instead of falling back to a default value. - -Another hint: You can use the `map_err` method of `Result` with a function or a -closure to wrap the error from `parse::`. - -Yet another hint: If you would like to propagate errors by using the `?` -operator in your solution, you might want to look at -https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reenter_question_mark.html""" - -[[exercises]] -name = "try_from_into" -dir = "23_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? - -Challenge: Can you make the `TryFrom` implementations generic over many integer -types?""" - -[[exercises]] -name = "as_ref_mut" -dir = "23_conversions" -hint = """ -Add `AsRef` or `AsMut` as a trait bound to the functions.""" diff --git a/rustlings-macros/src/lib.rs b/rustlings-macros/src/lib.rs deleted file mode 100644 index 6c6067bc9d..0000000000 --- a/rustlings-macros/src/lib.rs +++ /dev/null @@ -1,56 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use serde::Deserialize; - -#[derive(Deserialize)] -struct ExerciseInfo { - name: String, - dir: String, -} - -#[derive(Deserialize)] -struct InfoFile { - exercises: Vec, -} - -#[proc_macro] -pub fn include_files(_: TokenStream) -> TokenStream { - let info_file = include_str!("../info.toml"); - let exercises = toml_edit::de::from_str::(info_file) - .expect("Failed to parse `info.toml`") - .exercises; - - let exercise_files = exercises - .iter() - .map(|exercise| format!("../exercises/{}/{}.rs", exercise.dir, exercise.name)); - let solution_files = exercises - .iter() - .map(|exercise| format!("../solutions/{}/{}.rs", exercise.dir, exercise.name)); - - let mut dirs = Vec::with_capacity(32); - let mut dir_inds = vec![0; exercises.len()]; - - for (exercise, dir_ind) in exercises.iter().zip(&mut dir_inds) { - // The directory is often the last one inserted. - if let Some(ind) = dirs.iter().rev().position(|dir| *dir == exercise.dir) { - *dir_ind = dirs.len() - 1 - ind; - continue; - } - - dirs.push(exercise.dir.as_str()); - *dir_ind = dirs.len() - 1; - } - - let readmes = dirs - .iter() - .map(|dir| format!("../exercises/{dir}/README.md")); - - quote! { - EmbeddedFiles { - info_file: #info_file, - exercise_files: &[#(ExerciseFiles { exercise: include_bytes!(#exercise_files), solution: include_bytes!(#solution_files), dir_ind: #dir_inds }),*], - exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*] - } - } - .into() -} diff --git a/solutions/01_variables/variables5.rs b/solutions/01_variables/variables5.rs index 0ea3903013..9057754ce4 100644 --- a/solutions/01_variables/variables5.rs +++ b/solutions/01_variables/variables5.rs @@ -1,6 +1,6 @@ fn main() { let number = "T-H-R-E-E"; - println!("Spell a number: {number}"); + println!("Spell a number: {}", number); // Using variable shadowing // https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing diff --git a/solutions/03_if/if1.rs b/solutions/03_if/if1.rs index 8512a60ff1..079c671584 100644 --- a/solutions/03_if/if1.rs +++ b/solutions/03_if/if1.rs @@ -1,5 +1,9 @@ fn bigger(a: i32, b: i32) -> i32 { - if a > b { a } else { b } + if a > b { + a + } else { + b + } } fn main() { diff --git a/solutions/06_move_semantics/move_semantics4.rs b/solutions/06_move_semantics/move_semantics4.rs index 1a39d4fc8a..64fdd9dbde 100644 --- a/solutions/06_move_semantics/move_semantics4.rs +++ b/solutions/06_move_semantics/move_semantics4.rs @@ -4,6 +4,8 @@ fn main() { #[cfg(test)] mod tests { + // TODO: Fix the compiler errors only by reordering the lines in the test. + // Don't add, change or remove any line. #[test] fn move_semantics4() { let mut x = Vec::new(); diff --git a/solutions/09_strings/strings3.rs b/solutions/09_strings/strings3.rs index ee6b56afe1..a478e62ad0 100644 --- a/solutions/09_strings/strings3.rs +++ b/solutions/09_strings/strings3.rs @@ -26,7 +26,6 @@ mod tests { assert_eq!(trim_me("Hello! "), "Hello!"); assert_eq!(trim_me(" What's up!"), "What's up!"); assert_eq!(trim_me(" Hola! "), "Hola!"); - assert_eq!(trim_me("Hi!"), "Hi!"); } #[test] diff --git a/solutions/11_hashmaps/hashmaps3.rs b/solutions/11_hashmaps/hashmaps3.rs index 485bf83034..433b16c331 100644 --- a/solutions/11_hashmaps/hashmaps3.rs +++ b/solutions/11_hashmaps/hashmaps3.rs @@ -60,11 +60,9 @@ England,Spain,1,0"; fn build_scores() { let scores = build_scores_table(RESULTS); - assert!( - ["England", "France", "Germany", "Italy", "Poland", "Spain"] - .into_iter() - .all(|team_name| scores.contains_key(team_name)) - ); + assert!(["England", "France", "Germany", "Italy", "Poland", "Spain"] + .into_iter() + .all(|team_name| scores.contains_key(team_name))); } #[test] diff --git a/solutions/12_options/options3.rs b/solutions/12_options/options3.rs index c918f711dc..0081eeb2c7 100644 --- a/solutions/12_options/options3.rs +++ b/solutions/12_options/options3.rs @@ -10,7 +10,7 @@ fn main() { // Solution 1: Matching over the `Option` (not `&Option`) but without moving // out of the `Some` variant. match optional_point { - Some(ref p) => println!("Coordinates are {},{}", p.x, p.y), + Some(ref p) => println!("Co-ordinates are {},{}", p.x, p.y), // ^^^ added _ => panic!("No match!"), } @@ -18,8 +18,7 @@ fn main() { // Solution 2: Matching over a reference (`&Option`) by added `&` before // `optional_point`. match &optional_point { - //^ added - Some(p) => println!("Coordinates are {},{}", p.x, p.y), + Some(p) => println!("Co-ordinates are {},{}", p.x, p.y), _ => panic!("No match!"), } diff --git a/solutions/13_error_handling/errors2.rs b/solutions/13_error_handling/errors2.rs index f0e144e746..0597c8c9d9 100644 --- a/solutions/13_error_handling/errors2.rs +++ b/solutions/13_error_handling/errors2.rs @@ -16,7 +16,7 @@ use std::num::ParseIntError; -#[allow(unused_variables, clippy::question_mark)] +#[allow(unused_variables)] fn total_cost(item_quantity: &str) -> Result { let processing_fee = 1; let cost_per_item = 5; diff --git a/solutions/13_error_handling/errors6.rs b/solutions/13_error_handling/errors6.rs index ce18073afb..86793619f2 100644 --- a/solutions/13_error_handling/errors6.rs +++ b/solutions/13_error_handling/errors6.rs @@ -29,21 +29,6 @@ impl ParsePosNonzeroError { } } -// As an alternative solution, implementing the `From` trait allows for the -// automatic conversion from a `ParseIntError` into a `ParsePosNonzeroError` -// using the `?` operator, without the need to call `map_err`. -// -// ``` -// let x: i64 = s.parse()?; -// ``` -// -// Traits like `From` will be dealt with in later exercises. -impl From for ParsePosNonzeroError { - fn from(err: ParseIntError) -> Self { - ParsePosNonzeroError::ParseInt(err) - } -} - #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); diff --git a/solutions/14_generics/generics2.rs b/solutions/14_generics/generics2.rs index 14f3f7afea..dcf2377a2f 100644 --- a/solutions/14_generics/generics2.rs +++ b/solutions/14_generics/generics2.rs @@ -1,28 +1,4 @@ -struct Wrapper { - value: T, -} - -impl Wrapper { - fn new(value: T) -> Self { - Wrapper { value } - } -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn store_u32_in_wrapper() { - assert_eq!(Wrapper::new(42).value, 42); - } - - #[test] - fn store_str_in_wrapper() { - assert_eq!(Wrapper::new("Foo").value, "Foo"); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/15_traits/traits1.rs b/solutions/15_traits/traits1.rs index 790873f4da..dcf2377a2f 100644 --- a/solutions/15_traits/traits1.rs +++ b/solutions/15_traits/traits1.rs @@ -1,32 +1,4 @@ -// The trait `AppendBar` has only one function which appends "Bar" to any object -// implementing this trait. -trait AppendBar { - fn append_bar(self) -> Self; -} - -impl AppendBar for String { - fn append_bar(self) -> Self { - self + "Bar" - } -} - fn main() { - let s = String::from("Foo"); - let s = s.append_bar(); - println!("s: {s}"); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn is_foo_bar() { - assert_eq!(String::from("Foo").append_bar(), "FooBar"); - } - - #[test] - fn is_bar_bar() { - assert_eq!(String::from("").append_bar().append_bar(), "BarBar"); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/15_traits/traits2.rs b/solutions/15_traits/traits2.rs index 0db93e0f4f..dcf2377a2f 100644 --- a/solutions/15_traits/traits2.rs +++ b/solutions/15_traits/traits2.rs @@ -1,27 +1,4 @@ -trait AppendBar { - fn append_bar(self) -> Self; -} - -impl AppendBar for Vec { - fn append_bar(mut self) -> Self { - // ^^^ this is important - self.push(String::from("Bar")); - self - } -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn is_vec_pop_eq_bar() { - let mut foo = vec![String::from("Foo")].append_bar(); - assert_eq!(foo.pop().unwrap(), "Bar"); - assert_eq!(foo.pop().unwrap(), "Foo"); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/15_traits/traits3.rs b/solutions/15_traits/traits3.rs index 3d8ec85ead..dcf2377a2f 100644 --- a/solutions/15_traits/traits3.rs +++ b/solutions/15_traits/traits3.rs @@ -1,36 +1,4 @@ -trait Licensed { - fn licensing_info(&self) -> String { - "Default license".to_string() - } -} - -struct SomeSoftware { - version_number: i32, -} - -struct OtherSoftware { - version_number: String, -} - -impl Licensed for SomeSoftware {} -impl Licensed for OtherSoftware {} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn is_licensing_info_the_same() { - let licensing_info = "Default license"; - let some_software = SomeSoftware { version_number: 1 }; - let other_software = OtherSoftware { - version_number: "v2.0.0".to_string(), - }; - assert_eq!(some_software.licensing_info(), licensing_info); - assert_eq!(other_software.licensing_info(), licensing_info); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/15_traits/traits4.rs b/solutions/15_traits/traits4.rs index 3675b8dfe1..dcf2377a2f 100644 --- a/solutions/15_traits/traits4.rs +++ b/solutions/15_traits/traits4.rs @@ -1,35 +1,4 @@ -trait Licensed { - fn licensing_info(&self) -> String { - "Default license".to_string() - } -} - -struct SomeSoftware; -struct OtherSoftware; - -impl Licensed for SomeSoftware {} -impl Licensed for OtherSoftware {} - -fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> bool { - // ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ - software1.licensing_info() == software2.licensing_info() -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn compare_license_information() { - assert!(compare_license_types(SomeSoftware, OtherSoftware)); - } - - #[test] - fn compare_license_information_backwards() { - assert!(compare_license_types(OtherSoftware, SomeSoftware)); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/15_traits/traits5.rs b/solutions/15_traits/traits5.rs index 1fb426a474..dcf2377a2f 100644 --- a/solutions/15_traits/traits5.rs +++ b/solutions/15_traits/traits5.rs @@ -1,39 +1,4 @@ -trait SomeTrait { - fn some_function(&self) -> bool { - true - } -} - -trait OtherTrait { - fn other_function(&self) -> bool { - true - } -} - -struct SomeStruct; -impl SomeTrait for SomeStruct {} -impl OtherTrait for SomeStruct {} - -struct OtherStruct; -impl SomeTrait for OtherStruct {} -impl OtherTrait for OtherStruct {} - -fn some_func(item: impl SomeTrait + OtherTrait) -> bool { - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - item.some_function() && item.other_function() -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_some_func() { - assert!(some_func(SomeStruct)); - assert!(some_func(OtherStruct)); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/16_lifetimes/lifetimes1.rs b/solutions/16_lifetimes/lifetimes1.rs index 4f56834f9d..dcf2377a2f 100644 --- a/solutions/16_lifetimes/lifetimes1.rs +++ b/solutions/16_lifetimes/lifetimes1.rs @@ -1,24 +1,4 @@ -// The Rust compiler needs to know how to check whether supplied references are -// valid, so that it can let the programmer know if a reference is at risk of -// going out of scope before it is used. Remember, references are borrows and do -// not own their own data. What if their owner goes out of scope? - -fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { - // ^^^^ ^^ ^^ ^^ - if x.len() > y.len() { x } else { y } -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_longest() { - assert_eq!(longest("abcd", "123"), "abcd"); - assert_eq!(longest("abc", "1234"), "1234"); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/16_lifetimes/lifetimes2.rs b/solutions/16_lifetimes/lifetimes2.rs index 3ca49093e8..dcf2377a2f 100644 --- a/solutions/16_lifetimes/lifetimes2.rs +++ b/solutions/16_lifetimes/lifetimes2.rs @@ -1,29 +1,4 @@ -fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { - if x.len() > y.len() { x } else { y } -} - fn main() { - let string1 = String::from("long string is long"); - // Solution1: You can move `strings2` out of the inner block so that it is - // not dropped before the print statement. - let string2 = String::from("xyz"); - let result; - { - result = longest(&string1, &string2); - } - println!("The longest string is '{result}'"); - // `string2` dropped at the end of the function. - - // ========================================================================= - - let string1 = String::from("long string is long"); - let result; - { - let string2 = String::from("xyz"); - result = longest(&string1, &string2); - // Solution2: You can move the print statement into the inner block so - // that it is executed before `string2` is dropped. - println!("The longest string is '{result}'"); - // `string2` dropped here (end of the inner scope). - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/16_lifetimes/lifetimes3.rs b/solutions/16_lifetimes/lifetimes3.rs index 16a5a6840c..dcf2377a2f 100644 --- a/solutions/16_lifetimes/lifetimes3.rs +++ b/solutions/16_lifetimes/lifetimes3.rs @@ -1,18 +1,4 @@ -// Lifetimes are also needed when structs hold references. - -struct Book<'a> { - // ^^^^ added a lifetime annotation - author: &'a str, - // ^^ - title: &'a str, - // ^^ -} - fn main() { - let book = Book { - author: "George Orwell", - title: "1984", - }; - - println!("{} by {}", book.title, book.author); + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/17_tests/tests1.rs b/solutions/17_tests/tests1.rs index c52b8b16d7..dcf2377a2f 100644 --- a/solutions/17_tests/tests1.rs +++ b/solutions/17_tests/tests1.rs @@ -1,24 +1,4 @@ -// Tests are important to ensure that your code does what you think it should -// do. - -fn is_even(n: i64) -> bool { - n % 2 == 0 -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - // When writing unit tests, it is common to import everything from the outer - // module (`super`) using a wildcard. - use super::*; - - #[test] - fn you_can_assert() { - assert!(is_even(0)); - assert!(!is_even(-1)); - // ^ You can assert `false` using the negation operator `!`. - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/17_tests/tests2.rs b/solutions/17_tests/tests2.rs index 39a0005e8e..dcf2377a2f 100644 --- a/solutions/17_tests/tests2.rs +++ b/solutions/17_tests/tests2.rs @@ -1,22 +1,4 @@ -// Calculates the power of 2 using a bit shift. -// `1 << n` is equivalent to "2 to the power of n". -fn power_of_2(n: u8) -> u64 { - 1 << n -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn you_can_assert_eq() { - assert_eq!(power_of_2(0), 1); - assert_eq!(power_of_2(1), 2); - assert_eq!(power_of_2(2), 4); - assert_eq!(power_of_2(3), 8); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/17_tests/tests3.rs b/solutions/17_tests/tests3.rs index 487fdc66ea..dcf2377a2f 100644 --- a/solutions/17_tests/tests3.rs +++ b/solutions/17_tests/tests3.rs @@ -1,45 +1,4 @@ -struct Rectangle { - width: i32, - height: i32, -} - -impl Rectangle { - // Don't change this function. - fn new(width: i32, height: i32) -> Self { - if width <= 0 || height <= 0 { - // Returning a `Result` would be better here. But we want to learn - // how to test functions that can panic. - panic!("Rectangle width and height must be positive"); - } - - Rectangle { width, height } - } -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn correct_width_and_height() { - let rect = Rectangle::new(10, 20); - assert_eq!(rect.width, 10); // Check width - assert_eq!(rect.height, 20); // Check height - } - - #[test] - #[should_panic] // Added this attribute to check that the test panics. - fn negative_width() { - let _rect = Rectangle::new(-10, 10); - } - - #[test] - #[should_panic] // Added this attribute to check that the test panics. - fn negative_height() { - let _rect = Rectangle::new(10, -10); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/18_iterators/iterators1.rs b/solutions/18_iterators/iterators1.rs index 93a6008aa5..dcf2377a2f 100644 --- a/solutions/18_iterators/iterators1.rs +++ b/solutions/18_iterators/iterators1.rs @@ -1,26 +1,4 @@ -// When performing operations on elements within a collection, iterators are -// essential. This module helps you get familiar with the structure of using an -// iterator and how to go through elements within an iterable collection. - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - #[test] - fn iterators() { - let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"]; - - // Create an iterator over the array. - let mut fav_fruits_iterator = my_fav_fruits.iter(); - - assert_eq!(fav_fruits_iterator.next(), Some(&"banana")); - assert_eq!(fav_fruits_iterator.next(), Some(&"custard apple")); - assert_eq!(fav_fruits_iterator.next(), Some(&"avocado")); - assert_eq!(fav_fruits_iterator.next(), Some(&"peach")); - assert_eq!(fav_fruits_iterator.next(), Some(&"raspberry")); - assert_eq!(fav_fruits_iterator.next(), None); - // ^^^^ reached the end - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/18_iterators/iterators2.rs b/solutions/18_iterators/iterators2.rs index db05f293d7..dcf2377a2f 100644 --- a/solutions/18_iterators/iterators2.rs +++ b/solutions/18_iterators/iterators2.rs @@ -1,56 +1,4 @@ -// In this exercise, you'll learn some of the unique advantages that iterators -// can offer. - -// "hello" -> "Hello" -fn capitalize_first(input: &str) -> String { - let mut chars = input.chars(); - match chars.next() { - None => String::new(), - Some(first) => first.to_uppercase().to_string() + chars.as_str(), - } -} - -// Apply the `capitalize_first` function to a slice of string slices. -// Return a vector of strings. -// ["hello", "world"] -> ["Hello", "World"] -fn capitalize_words_vector(words: &[&str]) -> Vec { - words.iter().map(|word| capitalize_first(word)).collect() -} - -// Apply the `capitalize_first` function again to a slice of string -// slices. Return a single string. -// ["hello", " ", "world"] -> "Hello World" -fn capitalize_words_string(words: &[&str]) -> String { - words.iter().map(|word| capitalize_first(word)).collect() -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_success() { - assert_eq!(capitalize_first("hello"), "Hello"); - } - - #[test] - fn test_empty() { - assert_eq!(capitalize_first(""), ""); - } - - #[test] - fn test_iterate_string_vec() { - let words = vec!["hello", "world"]; - assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]); - } - - #[test] - fn test_iterate_into_string() { - let words = vec!["hello", " ", "world"]; - assert_eq!(capitalize_words_string(&words), "Hello World"); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/18_iterators/iterators3.rs b/solutions/18_iterators/iterators3.rs index 11aa1ec8ce..dcf2377a2f 100644 --- a/solutions/18_iterators/iterators3.rs +++ b/solutions/18_iterators/iterators3.rs @@ -1,86 +1,4 @@ -#[derive(Debug, PartialEq, Eq)] -enum DivisionError { - // Example: 42 / 0 - DivideByZero, - // Only case for `i64`: `i64::MIN / -1` because the result is `i64::MAX + 1` - IntegerOverflow, - // Example: 5 / 2 = 2.5 - NotDivisible, -} - -fn divide(a: i64, b: i64) -> Result { - if b == 0 { - return Err(DivisionError::DivideByZero); - } - - if a == i64::MIN && b == -1 { - return Err(DivisionError::IntegerOverflow); - } - - if a % b != 0 { - return Err(DivisionError::NotDivisible); - } - - Ok(a / b) -} - -fn result_with_list() -> Result, DivisionError> { - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - let numbers = [27, 297, 38502, 81]; - let division_results = numbers.into_iter().map(|n| divide(n, 27)); - // Collects to the expected return type. Returns the first error in the - // division results (if one exists). - division_results.collect() -} - -fn list_of_results() -> Vec> { - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - let numbers = [27, 297, 38502, 81]; - let division_results = numbers.into_iter().map(|n| divide(n, 27)); - // Collects to the expected return type. - division_results.collect() -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_success() { - assert_eq!(divide(81, 9), Ok(9)); - } - - #[test] - fn test_divide_by_0() { - assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero)); - } - - #[test] - fn test_integer_overflow() { - assert_eq!(divide(i64::MIN, -1), Err(DivisionError::IntegerOverflow)); - } - - #[test] - fn test_not_divisible() { - assert_eq!(divide(81, 6), Err(DivisionError::NotDivisible)); - } - - #[test] - fn test_divide_0_by_something() { - assert_eq!(divide(0, 81), Ok(0)); - } - - #[test] - fn test_result_with_list() { - assert_eq!(result_with_list().unwrap(), [1, 11, 1426, 3]); - } - - #[test] - fn test_list_of_results() { - assert_eq!(list_of_results(), [Ok(1), Ok(11), Ok(1426), Ok(3)]); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/18_iterators/iterators4.rs b/solutions/18_iterators/iterators4.rs index 4168835afc..dcf2377a2f 100644 --- a/solutions/18_iterators/iterators4.rs +++ b/solutions/18_iterators/iterators4.rs @@ -1,72 +1,4 @@ -// 3 possible solutions are presented. - -// With `for` loop and a mutable variable. -fn factorial_for(num: u64) -> u64 { - let mut result = 1; - - for x in 2..=num { - result *= x; - } - - result -} - -// Equivalent to `factorial_for` but shorter and without a `for` loop and -// mutable variables. -fn factorial_fold(num: u64) -> u64 { - // Case num==0: The iterator 2..=0 is empty - // -> The initial value of `fold` is returned which is 1. - // Case num==1: The iterator 2..=1 is also empty - // -> The initial value 1 is returned. - // Case num==2: The iterator 2..=2 contains one element - // -> The initial value 1 is multiplied by 2 and the result - // is returned. - // Case num==3: The iterator 2..=3 contains 2 elements - // -> 1 * 2 is calculated, then the result 2 is multiplied by - // the second element 3 so the result 6 is returned. - // And so on… - #[allow(clippy::unnecessary_fold)] - (2..=num).fold(1, |acc, x| acc * x) -} - -// Equivalent to `factorial_fold` but with a built-in method that is suggested -// by Clippy. -fn factorial_product(num: u64) -> u64 { - (2..=num).product() -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn factorial_of_0() { - assert_eq!(factorial_for(0), 1); - assert_eq!(factorial_fold(0), 1); - assert_eq!(factorial_product(0), 1); - } - - #[test] - fn factorial_of_1() { - assert_eq!(factorial_for(1), 1); - assert_eq!(factorial_fold(1), 1); - assert_eq!(factorial_product(1), 1); - } - #[test] - fn factorial_of_2() { - assert_eq!(factorial_for(2), 2); - assert_eq!(factorial_fold(2), 2); - assert_eq!(factorial_product(2), 2); - } - - #[test] - fn factorial_of_4() { - assert_eq!(factorial_for(4), 24); - assert_eq!(factorial_fold(4), 24); - assert_eq!(factorial_product(4), 24); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/18_iterators/iterators5.rs b/solutions/18_iterators/iterators5.rs index 067a117b92..dcf2377a2f 100644 --- a/solutions/18_iterators/iterators5.rs +++ b/solutions/18_iterators/iterators5.rs @@ -1,168 +1,4 @@ -// Let's define a simple model to track Rustlings' exercise progress. Progress -// will be modelled using a hash map. The name of the exercise is the key and -// the progress is the value. Two counting functions were created to count the -// number of exercises with a given progress. Recreate this counting -// functionality using iterators. Try to not use imperative loops (for/while). - -use std::collections::HashMap; - -#[derive(Clone, Copy, PartialEq, Eq)] -enum Progress { - None, - Some, - Complete, -} - -fn count_for(map: &HashMap, value: Progress) -> usize { - let mut count = 0; - for val in map.values() { - if *val == value { - count += 1; - } - } - count -} - -fn count_iterator(map: &HashMap, value: Progress) -> usize { - // `map` is a hash map with `String` keys and `Progress` values. - // map = { "variables1": Complete, "from_str": None, … } - map.values().filter(|val| **val == value).count() -} - -fn count_collection_for(collection: &[HashMap], value: Progress) -> usize { - let mut count = 0; - for map in collection { - count += count_for(map, value); - } - count -} - -fn count_collection_iterator(collection: &[HashMap], value: Progress) -> usize { - // `collection` is a slice of hash maps. - // collection = [{ "variables1": Complete, "from_str": None, … }, - // { "variables2": Complete, … }, … ] - collection - .iter() - .map(|map| count_iterator(map, value)) - .sum() -} - -// Equivalent to `count_collection_iterator` and `count_iterator`, iterating as -// if the collection was a single container instead of a container of containers -// (and more accurately, a single iterator instead of an iterator of iterators). -fn count_collection_iterator_flat( - collection: &[HashMap], - value: Progress, -) -> usize { - // `collection` is a slice of hash maps. - // collection = [{ "variables1": Complete, "from_str": None, … }, - // { "variables2": Complete, … }, … ] - collection - .iter() - .flat_map(HashMap::values) // or just `.flatten()` when wanting the default iterator (`HashMap::iter`) - .filter(|val| **val == value) - .count() -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - use Progress::*; - - fn get_map() -> HashMap { - let mut map = HashMap::new(); - map.insert(String::from("variables1"), Complete); - map.insert(String::from("functions1"), Complete); - map.insert(String::from("hashmap1"), Complete); - map.insert(String::from("arc1"), Some); - map.insert(String::from("as_ref_mut"), None); - map.insert(String::from("from_str"), None); - - map - } - - fn get_vec_map() -> Vec> { - let map = get_map(); - - let mut other = HashMap::new(); - other.insert(String::from("variables2"), Complete); - other.insert(String::from("functions2"), Complete); - other.insert(String::from("if1"), Complete); - other.insert(String::from("from_into"), None); - other.insert(String::from("try_from_into"), None); - - vec![map, other] - } - - #[test] - fn count_complete() { - let map = get_map(); - assert_eq!(count_iterator(&map, Complete), 3); - } - - #[test] - fn count_some() { - let map = get_map(); - assert_eq!(count_iterator(&map, Some), 1); - } - - #[test] - fn count_none() { - let map = get_map(); - assert_eq!(count_iterator(&map, None), 2); - } - - #[test] - fn count_complete_equals_for() { - let map = get_map(); - let progress_states = [Complete, Some, None]; - for progress_state in progress_states { - assert_eq!( - count_for(&map, progress_state), - count_iterator(&map, progress_state), - ); - } - } - - #[test] - fn count_collection_complete() { - let collection = get_vec_map(); - assert_eq!(count_collection_iterator(&collection, Complete), 6); - assert_eq!(count_collection_iterator_flat(&collection, Complete), 6); - } - - #[test] - fn count_collection_some() { - let collection = get_vec_map(); - assert_eq!(count_collection_iterator(&collection, Some), 1); - assert_eq!(count_collection_iterator_flat(&collection, Some), 1); - } - - #[test] - fn count_collection_none() { - let collection = get_vec_map(); - assert_eq!(count_collection_iterator(&collection, None), 4); - assert_eq!(count_collection_iterator_flat(&collection, None), 4); - } - - #[test] - fn count_collection_equals_for() { - let collection = get_vec_map(); - let progress_states = [Complete, Some, None]; - - for progress_state in progress_states { - assert_eq!( - count_collection_for(&collection, progress_state), - count_collection_iterator(&collection, progress_state), - ); - assert_eq!( - count_collection_for(&collection, progress_state), - count_collection_iterator_flat(&collection, progress_state), - ); - } - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/19_smart_pointers/arc1.rs b/solutions/19_smart_pointers/arc1.rs index bd76189fee..dcf2377a2f 100644 --- a/solutions/19_smart_pointers/arc1.rs +++ b/solutions/19_smart_pointers/arc1.rs @@ -1,45 +1,4 @@ -// In this exercise, we are given a `Vec` of `u32` called `numbers` with values -// ranging from 0 to 99. We would like to use this set of numbers within 8 -// different threads simultaneously. Each thread is going to get the sum of -// every eighth value with an offset. -// -// The first thread (offset 0), will sum 0, 8, 16, … -// The second thread (offset 1), will sum 1, 9, 17, … -// The third thread (offset 2), will sum 2, 10, 18, … -// … -// The eighth thread (offset 7), will sum 7, 15, 23, … -// -// Each thread should own a reference-counting pointer to the vector of -// numbers. But `Rc` isn't thread-safe. Therefore, we need to use `Arc`. -// -// Don't get distracted by how threads are spawned and joined. We will practice -// that later in the exercises about threads. - -// Don't change the lines below. -#![forbid(unused_imports)] -use std::{sync::Arc, thread}; - fn main() { - let numbers: Vec<_> = (0..100u32).collect(); - - let shared_numbers = Arc::new(numbers); - // ^^^^^^^^^^^^^^^^^ - - let mut join_handles = Vec::new(); - - for offset in 0..8 { - let child_numbers = Arc::clone(&shared_numbers); - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - let handle = thread::spawn(move || { - let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum(); - println!("Sum of offset {offset} is {sum}"); - }); - - join_handles.push(handle); - } - - for handle in join_handles.into_iter() { - handle.join().unwrap(); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/19_smart_pointers/box1.rs b/solutions/19_smart_pointers/box1.rs index 189cc5623a..dcf2377a2f 100644 --- a/solutions/19_smart_pointers/box1.rs +++ b/solutions/19_smart_pointers/box1.rs @@ -1,47 +1,4 @@ -// At compile time, Rust needs to know how much space a type takes up. This -// becomes problematic for recursive types, where a value can have as part of -// itself another value of the same type. To get around the issue, we can use a -// `Box` - a smart pointer used to store data on the heap, which also allows us -// to wrap a recursive type. -// -// The recursive type we're implementing in this exercise is the "cons list", a -// data structure frequently found in functional programming languages. Each -// item in a cons list contains two elements: The value of the current item and -// the next item. The last item is a value called `Nil`. - -#[derive(PartialEq, Debug)] -enum List { - Cons(i32, Box), - Nil, -} - -fn create_empty_list() -> List { - List::Nil -} - -fn create_non_empty_list() -> List { - List::Cons(42, Box::new(List::Nil)) -} - fn main() { - println!("This is an empty cons list: {:?}", create_empty_list()); - println!( - "This is a non-empty cons list: {:?}", - create_non_empty_list(), - ); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_create_empty_list() { - assert_eq!(create_empty_list(), List::Nil); - } - - #[test] - fn test_create_non_empty_list() { - assert_ne!(create_empty_list(), create_non_empty_list()); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/19_smart_pointers/cow1.rs b/solutions/19_smart_pointers/cow1.rs index 461143be86..dcf2377a2f 100644 --- a/solutions/19_smart_pointers/cow1.rs +++ b/solutions/19_smart_pointers/cow1.rs @@ -1,69 +1,4 @@ -// This exercise explores the `Cow` (Clone-On-Write) smart pointer. It can -// enclose and provide immutable access to borrowed data and clone the data -// lazily when mutation or ownership is required. The type is designed to work -// with general borrowed data via the `Borrow` trait. - -use std::borrow::Cow; - -fn abs_all(input: &mut Cow<[i32]>) { - for ind in 0..input.len() { - let value = input[ind]; - if value < 0 { - // Clones into a vector if not already owned. - input.to_mut()[ind] = -value; - } - } -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn reference_mutation() { - // Clone occurs because `input` needs to be mutated. - let vec = vec![-1, 0, 1]; - let mut input = Cow::from(&vec); - abs_all(&mut input); - assert!(matches!(input, Cow::Owned(_))); - } - - #[test] - fn reference_no_mutation() { - // No clone occurs because `input` doesn't need to be mutated. - let vec = vec![0, 1, 2]; - let mut input = Cow::from(&vec); - abs_all(&mut input); - assert!(matches!(input, Cow::Borrowed(_))); - // ^^^^^^^^^^^^^^^^ - } - - #[test] - fn owned_no_mutation() { - // We can also pass `vec` without `&` so `Cow` owns it directly. In this - // case, no mutation occurs (all numbers are already absolute) and thus - // also no clone. But the result is still owned because it was never - // borrowed or mutated. - let vec = vec![0, 1, 2]; - let mut input = Cow::from(vec); - abs_all(&mut input); - assert!(matches!(input, Cow::Owned(_))); - // ^^^^^^^^^^^^^ - } - - #[test] - fn owned_mutation() { - // Of course this is also the case if a mutation does occur (not all - // numbers are absolute). In this case, the call to `to_mut()` in the - // `abs_all` function returns a reference to the same data as before. - let vec = vec![-1, 0, 1]; - let mut input = Cow::from(vec); - abs_all(&mut input); - assert!(matches!(input, Cow::Owned(_))); - // ^^^^^^^^^^^^^ - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/19_smart_pointers/rc1.rs b/solutions/19_smart_pointers/rc1.rs index edf40ebe11..dcf2377a2f 100644 --- a/solutions/19_smart_pointers/rc1.rs +++ b/solutions/19_smart_pointers/rc1.rs @@ -1,102 +1,4 @@ -// In this exercise, we want to express the concept of multiple owners via the -// `Rc` type. This is a model of our solar system - there is a `Sun` type and -// multiple `Planet`s. The planets take ownership of the sun, indicating that -// they revolve around the sun. - -use std::rc::Rc; - -#[derive(Debug)] -struct Sun; - -#[derive(Debug)] -enum Planet { - Mercury(Rc), - Venus(Rc), - Earth(Rc), - Mars(Rc), - Jupiter(Rc), - Saturn(Rc), - Uranus(Rc), - Neptune(Rc), -} - -impl Planet { - fn details(&self) { - println!("Hi from {self:?}!"); - } -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn rc1() { - let sun = Rc::new(Sun); - println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference - - let mercury = Planet::Mercury(Rc::clone(&sun)); - println!("reference count = {}", Rc::strong_count(&sun)); // 2 references - mercury.details(); - - let venus = Planet::Venus(Rc::clone(&sun)); - println!("reference count = {}", Rc::strong_count(&sun)); // 3 references - venus.details(); - - let earth = Planet::Earth(Rc::clone(&sun)); - println!("reference count = {}", Rc::strong_count(&sun)); // 4 references - earth.details(); - - let mars = Planet::Mars(Rc::clone(&sun)); - println!("reference count = {}", Rc::strong_count(&sun)); // 5 references - mars.details(); - - let jupiter = Planet::Jupiter(Rc::clone(&sun)); - println!("reference count = {}", Rc::strong_count(&sun)); // 6 references - jupiter.details(); - - let saturn = Planet::Saturn(Rc::clone(&sun)); - println!("reference count = {}", Rc::strong_count(&sun)); // 7 references - saturn.details(); - - let uranus = Planet::Uranus(Rc::clone(&sun)); - println!("reference count = {}", Rc::strong_count(&sun)); // 8 references - uranus.details(); - - let neptune = Planet::Neptune(Rc::clone(&sun)); - println!("reference count = {}", Rc::strong_count(&sun)); // 9 references - neptune.details(); - - assert_eq!(Rc::strong_count(&sun), 9); - - drop(neptune); - println!("reference count = {}", Rc::strong_count(&sun)); // 8 references - - drop(uranus); - println!("reference count = {}", Rc::strong_count(&sun)); // 7 references - - drop(saturn); - println!("reference count = {}", Rc::strong_count(&sun)); // 6 references - - drop(jupiter); - println!("reference count = {}", Rc::strong_count(&sun)); // 5 references - - drop(mars); - println!("reference count = {}", Rc::strong_count(&sun)); // 4 references - - drop(earth); - println!("reference count = {}", Rc::strong_count(&sun)); // 3 references - - drop(venus); - println!("reference count = {}", Rc::strong_count(&sun)); // 2 references - - drop(mercury); - println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference - - assert_eq!(Rc::strong_count(&sun), 1); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/20_threads/threads1.rs b/solutions/20_threads/threads1.rs index 1fc5bc9c06..dcf2377a2f 100644 --- a/solutions/20_threads/threads1.rs +++ b/solutions/20_threads/threads1.rs @@ -1,37 +1,4 @@ -// This program spawns multiple threads that each runs for at least 250ms, and -// each thread returns how much time it took to complete. The program should -// wait until all the spawned threads have finished and should collect their -// return values into a vector. - -use std::{ - thread, - time::{Duration, Instant}, -}; - fn main() { - let mut handles = Vec::new(); - for i in 0..10 { - let handle = thread::spawn(move || { - let start = Instant::now(); - thread::sleep(Duration::from_millis(250)); - println!("Thread {i} done"); - start.elapsed().as_millis() - }); - handles.push(handle); - } - - let mut results = Vec::new(); - for handle in handles { - // Collect the results of all threads into the `results` vector. - results.push(handle.join().unwrap()); - } - - if results.len() != 10 { - panic!("Oh no! Some thread isn't done yet!"); - } - - println!(); - for (i, result) in results.into_iter().enumerate() { - println!("Thread {i} took {result}ms"); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/20_threads/threads2.rs b/solutions/20_threads/threads2.rs index bc268d63f6..dcf2377a2f 100644 --- a/solutions/20_threads/threads2.rs +++ b/solutions/20_threads/threads2.rs @@ -1,41 +1,4 @@ -// Building on the last exercise, we want all of the threads to complete their -// work. But this time, the spawned threads need to be in charge of updating a -// shared value: `JobStatus.jobs_done` - -use std::{ - sync::{Arc, Mutex}, - thread, - time::Duration, -}; - -struct JobStatus { - jobs_done: u32, -} - fn main() { - // `Arc` isn't enough if you want a **mutable** shared state. - // We need to wrap the value with a `Mutex`. - let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 })); - // ^^^^^^^^^^^ ^ - - let mut handles = Vec::new(); - for _ in 0..10 { - let status_shared = Arc::clone(&status); - let handle = thread::spawn(move || { - thread::sleep(Duration::from_millis(250)); - - // Lock before you update a shared value. - status_shared.lock().unwrap().jobs_done += 1; - // ^^^^^^^^^^^^^^^^ - }); - handles.push(handle); - } - - // Waiting for all jobs to complete. - for handle in handles { - handle.join().unwrap(); - } - - println!("Jobs done: {}", status.lock().unwrap().jobs_done); - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/20_threads/threads3.rs b/solutions/20_threads/threads3.rs index 7ceefea016..dcf2377a2f 100644 --- a/solutions/20_threads/threads3.rs +++ b/solutions/20_threads/threads3.rs @@ -1,62 +1,4 @@ -use std::{sync::mpsc, thread, time::Duration}; - -struct Queue { - first_half: Vec, - second_half: Vec, -} - -impl Queue { - fn new() -> Self { - Self { - first_half: vec![1, 2, 3, 4, 5], - second_half: vec![6, 7, 8, 9, 10], - } - } -} - -fn send_tx(q: Queue, tx: mpsc::Sender) { - // Clone the sender `tx` first. - let tx_clone = tx.clone(); - thread::spawn(move || { - for val in q.first_half { - println!("Sending {val:?}"); - // Then use the clone in the first thread. This means that - // `tx_clone` is moved to the first thread and `tx` to the second. - tx_clone.send(val).unwrap(); - thread::sleep(Duration::from_millis(250)); - } - }); - - thread::spawn(move || { - for val in q.second_half { - println!("Sending {val:?}"); - tx.send(val).unwrap(); - thread::sleep(Duration::from_millis(250)); - } - }); -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn threads3() { - let (tx, rx) = mpsc::channel(); - let queue = Queue::new(); - - send_tx(queue, tx); - - let mut received = Vec::with_capacity(10); - for value in rx { - received.push(value); - } - - received.sort(); - assert_eq!(received, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/21_macros/macros1.rs b/solutions/21_macros/macros1.rs index 1b861564be..dcf2377a2f 100644 --- a/solutions/21_macros/macros1.rs +++ b/solutions/21_macros/macros1.rs @@ -1,10 +1,4 @@ -macro_rules! my_macro { - () => { - println!("Check out my macro!"); - }; -} - fn main() { - my_macro!(); - // ^ + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/21_macros/macros2.rs b/solutions/21_macros/macros2.rs index b6fd5d2c56..dcf2377a2f 100644 --- a/solutions/21_macros/macros2.rs +++ b/solutions/21_macros/macros2.rs @@ -1,10 +1,4 @@ -// Moved the macro definition to be before its call. -macro_rules! my_macro { - () => { - println!("Check out my macro!"); - }; -} - fn main() { - my_macro!(); + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/21_macros/macros3.rs b/solutions/21_macros/macros3.rs index df35be4d9a..dcf2377a2f 100644 --- a/solutions/21_macros/macros3.rs +++ b/solutions/21_macros/macros3.rs @@ -1,13 +1,4 @@ -// Added the attribute `macro_use` attribute. -#[macro_use] -mod macros { - macro_rules! my_macro { - () => { - println!("Check out my macro!"); - }; - } -} - fn main() { - my_macro!(); + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/21_macros/macros4.rs b/solutions/21_macros/macros4.rs index 41bcad166a..dcf2377a2f 100644 --- a/solutions/21_macros/macros4.rs +++ b/solutions/21_macros/macros4.rs @@ -1,15 +1,4 @@ -// Added semicolons to separate the macro arms. -#[rustfmt::skip] -macro_rules! my_macro { - () => { - println!("Check out my macro!"); - }; - ($val:expr) => { - println!("Look at this other macro: {}", $val); - }; -} - fn main() { - my_macro!(); - my_macro!(7777); + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/22_clippy/clippy1.rs b/solutions/22_clippy/clippy1.rs index b9d1ec1729..dcf2377a2f 100644 --- a/solutions/22_clippy/clippy1.rs +++ b/solutions/22_clippy/clippy1.rs @@ -1,17 +1,4 @@ -// The Clippy tool is a collection of lints to analyze your code so you can -// catch common mistakes and improve your Rust code. -// -// For these exercises, the code will fail to compile when there are Clippy -// warnings. Check Clippy's suggestions from the output to solve the exercise. - -use std::f32::consts::PI; - fn main() { - // Use the more accurate `PI` constant. - let pi = PI; - let radius: f32 = 5.0; - - let area = pi * radius.powi(2); - - println!("The area of a circle with radius {radius:.2} is {area:.5}"); + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/22_clippy/clippy2.rs b/solutions/22_clippy/clippy2.rs index 7f6356285e..dcf2377a2f 100644 --- a/solutions/22_clippy/clippy2.rs +++ b/solutions/22_clippy/clippy2.rs @@ -1,10 +1,4 @@ fn main() { - let mut res = 42; - let option = Some(12); - // Use `if-let` instead of iteration. - if let Some(x) = option { - res += x; - } - - println!("{res}"); + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/22_clippy/clippy3.rs b/solutions/22_clippy/clippy3.rs index 81f381e02a..dcf2377a2f 100644 --- a/solutions/22_clippy/clippy3.rs +++ b/solutions/22_clippy/clippy3.rs @@ -1,31 +1,4 @@ -use std::mem; - -#[rustfmt::skip] -#[allow(unused_variables, unused_assignments)] fn main() { - let my_option: Option<&str> = None; - // `unwrap` of an `Option` after checking if it is `None` will panic. - // Use `if-let` instead. - if let Some(value) = my_option { - println!("{value}"); - } - - // A comma was missing. - let my_arr = &[ - -1, -2, -3, - -4, -5, -6, - ]; - println!("My array! Here it is: {my_arr:?}"); - - let mut my_empty_vec = vec![1, 2, 3, 4, 5]; - // `resize` mutates a vector instead of returning a new one. - // `resize(0, …)` clears a vector, so it is better to use `clear`. - my_empty_vec.clear(); - println!("This Vec is empty, see? {my_empty_vec:?}"); - - let mut value_a = 45; - let mut value_b = 66; - // Use `mem::swap` to correctly swap two values. - mem::swap(&mut value_a, &mut value_b); - println!("value a: {value_a}; value b: {value_b}"); + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/23_conversions/as_ref_mut.rs b/solutions/23_conversions/as_ref_mut.rs index a5d2d4fa57..dcf2377a2f 100644 --- a/solutions/23_conversions/as_ref_mut.rs +++ b/solutions/23_conversions/as_ref_mut.rs @@ -1,60 +1,4 @@ -// AsRef and AsMut allow for cheap reference-to-reference conversions. Read more -// about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and -// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively. - -// Obtain the number of bytes (not characters) in the given argument -// (`.len()` returns the number of bytes in a string). -fn byte_counter>(arg: T) -> usize { - arg.as_ref().len() -} - -// Obtain the number of characters (not bytes) in the given argument. -fn char_counter>(arg: T) -> usize { - arg.as_ref().chars().count() -} - -// Squares a number using `as_mut()`. -fn num_sq>(arg: &mut T) { - let arg = arg.as_mut(); - *arg *= *arg; -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn different_counts() { - let s = "Café au lait"; - assert_ne!(char_counter(s), byte_counter(s)); - } - - #[test] - fn same_counts() { - let s = "Cafe au lait"; - assert_eq!(char_counter(s), byte_counter(s)); - } - - #[test] - fn different_counts_using_string() { - let s = String::from("Café au lait"); - assert_ne!(char_counter(s.clone()), byte_counter(s)); - } - - #[test] - fn same_counts_using_string() { - let s = String::from("Cafe au lait"); - assert_eq!(char_counter(s.clone()), byte_counter(s)); - } - - #[test] - fn mut_box() { - let mut num: Box = Box::new(3); - num_sq(&mut num); - assert_eq!(*num, 9); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/23_conversions/from_into.rs b/solutions/23_conversions/from_into.rs index cec23cb4be..dcf2377a2f 100644 --- a/solutions/23_conversions/from_into.rs +++ b/solutions/23_conversions/from_into.rs @@ -1,136 +1,4 @@ -// The `From` trait is used for value-to-value conversions. If `From` is -// implemented, an implementation of `Into` is automatically provided. -// You can read more about it in the documentation: -// https://doc.rust-lang.org/std/convert/trait.From.html - -#[derive(Debug)] -struct Person { - name: String, - age: u8, -} - -// We implement the Default trait to use it as a fallback when the provided -// string is not convertible into a `Person` object. -impl Default for Person { - fn default() -> Self { - Self { - name: String::from("John"), - age: 30, - } - } -} - -impl From<&str> for Person { - fn from(s: &str) -> Self { - let mut split = s.split(','); - let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else { - // ^^^^ there should be no third element - return Self::default(); - }; - - if name.is_empty() { - return Self::default(); - } - - let Ok(age) = age.parse() else { - return Self::default(); - }; - - Self { - name: name.into(), - age, - } - } -} - fn main() { - // Use the `from` function. - let p1 = Person::from("Mark,20"); - println!("{p1:?}"); - - // Since `From` is implemented for Person, we are able to use `Into`. - let p2: Person = "Gerald,70".into(); - println!("{p2:?}"); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_default() { - let dp = Person::default(); - assert_eq!(dp.name, "John"); - assert_eq!(dp.age, 30); - } - - #[test] - fn test_bad_convert() { - let p = Person::from(""); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } - - #[test] - fn test_good_convert() { - let p = Person::from("Mark,20"); - assert_eq!(p.name, "Mark"); - assert_eq!(p.age, 20); - } - - #[test] - fn test_bad_age() { - let p = Person::from("Mark,twenty"); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } - - #[test] - fn test_missing_comma_and_age() { - let p: Person = Person::from("Mark"); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } - - #[test] - fn test_missing_age() { - let p: Person = Person::from("Mark,"); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } - - #[test] - fn test_missing_name() { - let p: Person = Person::from(",1"); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } - - #[test] - fn test_missing_name_and_age() { - let p: Person = Person::from(","); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } - - #[test] - fn test_missing_name_and_invalid_age() { - let p: Person = Person::from(",one"); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } - - #[test] - fn test_trailing_comma() { - let p: Person = Person::from("Mike,32,"); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } - - #[test] - fn test_trailing_comma_and_some_string() { - let p: Person = Person::from("Mike,32,dog"); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 30); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/23_conversions/from_str.rs b/solutions/23_conversions/from_str.rs index 005b50125a..dcf2377a2f 100644 --- a/solutions/23_conversions/from_str.rs +++ b/solutions/23_conversions/from_str.rs @@ -1,117 +1,4 @@ -// This is similar to the previous `from_into` exercise. But this time, we'll -// implement `FromStr` and return errors instead of falling back to a default -// value. Additionally, upon implementing `FromStr`, you can use the `parse` -// method on strings to generate an object of the implementor type. You can read -// more about it in the documentation: -// https://doc.rust-lang.org/std/str/trait.FromStr.html - -use std::num::ParseIntError; -use std::str::FromStr; - -#[derive(Debug, PartialEq)] -struct Person { - name: String, - age: u8, -} - -// We will use this error type for the `FromStr` implementation. -#[derive(Debug, PartialEq)] -enum ParsePersonError { - // Incorrect number of fields - BadLen, - // Empty name field - NoName, - // Wrapped error from parse::() - ParseInt(ParseIntError), -} - -impl FromStr for Person { - type Err = ParsePersonError; - - fn from_str(s: &str) -> Result { - let mut split = s.split(','); - let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else { - // ^^^^ there should be no third element - return Err(ParsePersonError::BadLen); - }; - - if name.is_empty() { - return Err(ParsePersonError::NoName); - } - - let age = age.parse().map_err(ParsePersonError::ParseInt)?; - - Ok(Self { - name: name.into(), - age, - }) - } -} - fn main() { - let p = "Mark,20".parse::(); - println!("{p:?}"); -} - -#[cfg(test)] -mod tests { - use super::*; - use ParsePersonError::*; - - #[test] - fn empty_input() { - assert_eq!("".parse::(), Err(BadLen)); - } - - #[test] - fn good_input() { - let p = "John,32".parse::(); - assert!(p.is_ok()); - let p = p.unwrap(); - assert_eq!(p.name, "John"); - assert_eq!(p.age, 32); - } - - #[test] - fn missing_age() { - assert!(matches!("John,".parse::(), Err(ParseInt(_)))); - } - - #[test] - fn invalid_age() { - assert!(matches!("John,twenty".parse::(), Err(ParseInt(_)))); - } - - #[test] - fn missing_comma_and_age() { - assert_eq!("John".parse::(), Err(BadLen)); - } - - #[test] - fn missing_name() { - assert_eq!(",1".parse::(), Err(NoName)); - } - - #[test] - fn missing_name_and_age() { - assert!(matches!(",".parse::(), Err(NoName | ParseInt(_)))); - } - - #[test] - fn missing_name_and_invalid_age() { - assert!(matches!( - ",one".parse::(), - Err(NoName | ParseInt(_)), - )); - } - - #[test] - fn trailing_comma() { - assert_eq!("John,32,".parse::(), Err(BadLen)); - } - - #[test] - fn trailing_comma_and_some_string() { - assert_eq!("John,32,man".parse::(), Err(BadLen)); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/23_conversions/try_from_into.rs b/solutions/23_conversions/try_from_into.rs index ee802eb06e..dcf2377a2f 100644 --- a/solutions/23_conversions/try_from_into.rs +++ b/solutions/23_conversions/try_from_into.rs @@ -1,193 +1,4 @@ -// `TryFrom` is a simple and safe type conversion that may fail in a controlled -// way under some circumstances. Basically, this is the same as `From`. The main -// difference is that this should return a `Result` type instead of the target -// type itself. You can read more about it in the documentation: -// https://doc.rust-lang.org/std/convert/trait.TryFrom.html - -#![allow(clippy::useless_vec)] -use std::convert::{TryFrom, TryInto}; - -#[derive(Debug, PartialEq)] -struct Color { - red: u8, - green: u8, - blue: u8, -} - -// We will use this error type for the `TryFrom` conversions. -#[derive(Debug, PartialEq)] -enum IntoColorError { - // Incorrect length of slice - BadLen, - // Integer conversion error - IntConversion, -} - -impl TryFrom<(i16, i16, i16)> for Color { - type Error = IntoColorError; - - fn try_from(tuple: (i16, i16, i16)) -> Result { - let (Ok(red), Ok(green), Ok(blue)) = ( - u8::try_from(tuple.0), - u8::try_from(tuple.1), - u8::try_from(tuple.2), - ) else { - return Err(IntoColorError::IntConversion); - }; - - Ok(Self { red, green, blue }) - } -} - -impl TryFrom<[i16; 3]> for Color { - type Error = IntoColorError; - - fn try_from(arr: [i16; 3]) -> Result { - // Reuse the implementation for a tuple. - Self::try_from((arr[0], arr[1], arr[2])) - } -} - -impl TryFrom<&[i16]> for Color { - type Error = IntoColorError; - - fn try_from(slice: &[i16]) -> Result { - // Check the length. - if slice.len() != 3 { - return Err(IntoColorError::BadLen); - } - - // Reuse the implementation for a tuple. - Self::try_from((slice[0], slice[1], slice[2])) - } -} - fn main() { - // Using the `try_from` function. - let c1 = Color::try_from((183, 65, 14)); - println!("{c1:?}"); - - // Since `TryFrom` is implemented for `Color`, we can use `TryInto`. - let c2: Result = [183, 65, 14].try_into(); - println!("{c2:?}"); - - let v = vec![183, 65, 14]; - // With slice we should use the `try_from` function - let c3 = Color::try_from(&v[..]); - println!("{c3:?}"); - // or put the slice within round brackets and use `try_into`. - let c4: Result = (&v[..]).try_into(); - println!("{c4:?}"); -} - -#[cfg(test)] -mod tests { - use super::*; - use IntoColorError::*; - - #[test] - fn test_tuple_out_of_range_positive() { - assert_eq!(Color::try_from((256, 1000, 10000)), Err(IntConversion)); - } - - #[test] - fn test_tuple_out_of_range_negative() { - assert_eq!(Color::try_from((-1, -10, -256)), Err(IntConversion)); - } - - #[test] - fn test_tuple_sum() { - assert_eq!(Color::try_from((-1, 255, 255)), Err(IntConversion)); - } - - #[test] - fn test_tuple_correct() { - let c: Result = (183, 65, 14).try_into(); - assert!(c.is_ok()); - assert_eq!( - c.unwrap(), - Color { - red: 183, - green: 65, - blue: 14, - } - ); - } - - #[test] - fn test_array_out_of_range_positive() { - let c: Result = [1000, 10000, 256].try_into(); - assert_eq!(c, Err(IntConversion)); - } - - #[test] - fn test_array_out_of_range_negative() { - let c: Result = [-10, -256, -1].try_into(); - assert_eq!(c, Err(IntConversion)); - } - - #[test] - fn test_array_sum() { - let c: Result = [-1, 255, 255].try_into(); - assert_eq!(c, Err(IntConversion)); - } - - #[test] - fn test_array_correct() { - let c: Result = [183, 65, 14].try_into(); - assert!(c.is_ok()); - assert_eq!( - c.unwrap(), - Color { - red: 183, - green: 65, - blue: 14 - } - ); - } - - #[test] - fn test_slice_out_of_range_positive() { - let arr = [10000, 256, 1000]; - assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); - } - - #[test] - fn test_slice_out_of_range_negative() { - let arr = [-256, -1, -10]; - assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); - } - - #[test] - fn test_slice_sum() { - let arr = [-1, 255, 255]; - assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); - } - - #[test] - fn test_slice_correct() { - let v = vec![183, 65, 14]; - let c: Result = Color::try_from(&v[..]); - assert!(c.is_ok()); - assert_eq!( - c.unwrap(), - Color { - red: 183, - green: 65, - blue: 14, - } - ); - } - - #[test] - fn test_slice_excess_length() { - let v = vec![0, 0, 0, 0]; - assert_eq!(Color::try_from(&v[..]), Err(BadLen)); - } - - #[test] - fn test_slice_insufficient_length() { - let v = vec![0, 0]; - assert_eq!(Color::try_from(&v[..]), Err(BadLen)); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/23_conversions/using_as.rs b/solutions/23_conversions/using_as.rs index 14b92ebf05..dcf2377a2f 100644 --- a/solutions/23_conversions/using_as.rs +++ b/solutions/23_conversions/using_as.rs @@ -1,24 +1,4 @@ -// Type casting in Rust is done via the usage of the `as` operator. -// Note that the `as` operator is not only used when type casting. It also helps -// with renaming imports. - -fn average(values: &[f64]) -> f64 { - let total = values.iter().sum::(); - total / values.len() as f64 - // ^^^^^^ -} - fn main() { - let values = [3.5, 0.3, 13.0, 11.7]; - println!("{}", average(&values)); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn returns_proper_type_and_value() { - assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/solutions/quizzes/quiz2.rs b/solutions/quizzes/quiz2.rs index 8b073b1867..58cbe4e2a6 100644 --- a/solutions/quizzes/quiz2.rs +++ b/solutions/quizzes/quiz2.rs @@ -62,8 +62,8 @@ mod tests { // Import `transformer`. use super::my_module::transformer; - use super::Command; use super::my_module::transformer_iter; + use super::Command; #[test] fn it_works() { diff --git a/solutions/quizzes/quiz3.rs b/solutions/quizzes/quiz3.rs index 7b9127820d..dcf2377a2f 100644 --- a/solutions/quizzes/quiz3.rs +++ b/solutions/quizzes/quiz3.rs @@ -1,65 +1,4 @@ -// An imaginary magical school has a new report card generation system written -// in Rust! Currently, the system only supports creating report cards where the -// student's grade is represented numerically (e.g. 1.0 -> 5.5). However, the -// school also issues alphabetical grades (A+ -> F-) and needs to be able to -// print both types of report card! -// -// Make the necessary code changes in the struct `ReportCard` and the impl -// block to support alphabetical report cards in addition to numerical ones. - -use std::fmt::Display; - -// Make the struct generic over `T`. -struct ReportCard { - // ^^^ - grade: T, - // ^ - student_name: String, - student_age: u8, -} - -// To be able to print the grade, it has to implement the `Display` trait. -impl ReportCard { - // ^^^^^^^ require that `T` implements `Display`. - fn print(&self) -> String { - format!( - "{} ({}) - achieved a grade of {}", - &self.student_name, &self.student_age, &self.grade, - ) - } -} - fn main() { - // You can optionally experiment here. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn generate_numeric_report_card() { - let report_card = ReportCard { - grade: 2.1, - student_name: "Tom Wriggle".to_string(), - student_age: 12, - }; - assert_eq!( - report_card.print(), - "Tom Wriggle (12) - achieved a grade of 2.1", - ); - } - - #[test] - fn generate_alphabetic_report_card() { - let report_card = ReportCard { - grade: "A+", - student_name: "Gary Plotter".to_string(), - student_age: 11, - }; - assert_eq!( - report_card.print(), - "Gary Plotter (11) - achieved a grade of A+", - ); - } + // DON'T EDIT THIS SOLUTION FILE! + // It will be automatically filled after you finish the exercise. } diff --git a/src/app_state.rs b/src/app_state.rs deleted file mode 100644 index f3f348133e..0000000000 --- a/src/app_state.rs +++ /dev/null @@ -1,650 +0,0 @@ -use anyhow::{Context, Error, Result, bail}; -use crossterm::{QueueableCommand, cursor, terminal}; -use std::{ - collections::HashSet, - env, - fs::{File, OpenOptions}, - io::{Read, Seek, StdoutLock, Write}, - path::{MAIN_SEPARATOR_STR, Path}, - process::{Command, Stdio}, - sync::{ - atomic::{AtomicUsize, Ordering::Relaxed}, - mpsc, - }, - thread, -}; - -use crate::{ - clear_terminal, - cmd::CmdRunner, - embedded::EMBEDDED_FILES, - exercise::{Exercise, RunnableExercise}, - info_file::ExerciseInfo, - term::{self, CheckProgressVisualizer}, -}; - -const STATE_FILE_NAME: &str = ".rustlings-state.txt"; -const DEFAULT_CHECK_PARALLELISM: usize = 8; - -#[must_use] -pub enum ExercisesProgress { - // All exercises are done. - AllDone, - // A new exercise is now pending. - NewPending, - // The current exercise is still pending. - CurrentPending, -} - -pub enum StateFileStatus { - Read, - NotRead, -} - -#[derive(Clone, Copy)] -pub enum CheckProgress { - None, - Checking, - Done, - Pending, -} - -pub struct AppState { - current_exercise_ind: usize, - exercises: Vec, - // Caches the number of done exercises to avoid iterating over all exercises every time. - n_done: u16, - final_message: String, - state_file: File, - // Preallocated buffer for reading and writing the state file. - file_buf: Vec, - official_exercises: bool, - cmd_runner: CmdRunner, - // Running in VS Code. - vs_code: bool, -} - -impl AppState { - pub fn new( - exercise_infos: Vec, - final_message: String, - ) -> Result<(Self, StateFileStatus)> { - let cmd_runner = CmdRunner::build()?; - let mut state_file = OpenOptions::new() - .create(true) - .read(true) - .write(true) - .truncate(false) - .open(STATE_FILE_NAME) - .with_context(|| { - format!("Failed to open or create the state file {STATE_FILE_NAME}") - })?; - - let dir_canonical_path = term::canonicalize("exercises"); - let mut exercises = exercise_infos - .into_iter() - .map(|exercise_info| { - // Leaking to be able to borrow in the watch mode `Table`. - // Leaking is not a problem because the `AppState` instance lives until - // the end of the program. - let path = exercise_info.path().leak(); - let name = exercise_info.name.leak(); - let dir = exercise_info.dir.map(|dir| &*dir.leak()); - let hint = exercise_info.hint.leak().trim_ascii(); - - let canonical_path = dir_canonical_path.as_deref().map(|dir_canonical_path| { - let mut canonical_path; - if let Some(dir) = dir { - canonical_path = String::with_capacity( - 2 + dir_canonical_path.len() + dir.len() + name.len(), - ); - canonical_path.push_str(dir_canonical_path); - canonical_path.push_str(MAIN_SEPARATOR_STR); - canonical_path.push_str(dir); - } else { - canonical_path = - String::with_capacity(1 + dir_canonical_path.len() + name.len()); - canonical_path.push_str(dir_canonical_path); - } - - canonical_path.push_str(MAIN_SEPARATOR_STR); - canonical_path.push_str(name); - canonical_path.push_str(".rs"); - canonical_path - }); - - Exercise { - dir, - name, - path, - canonical_path, - test: exercise_info.test, - strict_clippy: exercise_info.strict_clippy, - hint, - // Updated below. - done: false, - } - }) - .collect::>(); - - let mut current_exercise_ind = 0; - let mut n_done = 0; - let mut file_buf = Vec::with_capacity(2048); - let state_file_status = 'block: { - if state_file.read_to_end(&mut file_buf).is_err() { - break 'block StateFileStatus::NotRead; - } - - // See `Self::write` for more information about the file format. - let mut lines = file_buf.split(|c| *c == b'\n').skip(2); - - let Some(current_exercise_name) = lines.next() else { - break 'block StateFileStatus::NotRead; - }; - - if current_exercise_name.is_empty() || lines.next().is_none() { - break 'block StateFileStatus::NotRead; - } - - let mut done_exercises = HashSet::with_capacity(exercises.len()); - - for done_exercise_name in lines { - if done_exercise_name.is_empty() { - break; - } - done_exercises.insert(done_exercise_name); - } - - for (ind, exercise) in exercises.iter_mut().enumerate() { - if done_exercises.contains(exercise.name.as_bytes()) { - exercise.done = true; - n_done += 1; - } - - if exercise.name.as_bytes() == current_exercise_name { - current_exercise_ind = ind; - } - } - - StateFileStatus::Read - }; - - file_buf.clear(); - file_buf.extend_from_slice(STATE_FILE_HEADER); - - let slf = Self { - current_exercise_ind, - exercises, - n_done, - final_message, - state_file, - file_buf, - official_exercises: !Path::new("info.toml").exists(), - cmd_runner, - vs_code: env::var_os("TERM_PROGRAM").is_some_and(|v| v == "vscode"), - }; - - Ok((slf, state_file_status)) - } - - #[inline] - pub fn current_exercise_ind(&self) -> usize { - self.current_exercise_ind - } - - #[inline] - pub fn exercises(&self) -> &[Exercise] { - &self.exercises - } - - #[inline] - pub fn n_done(&self) -> u16 { - self.n_done - } - - #[inline] - pub fn n_pending(&self) -> u16 { - self.exercises.len() as u16 - self.n_done - } - - #[inline] - pub fn current_exercise(&self) -> &Exercise { - &self.exercises[self.current_exercise_ind] - } - - #[inline] - pub fn cmd_runner(&self) -> &CmdRunner { - &self.cmd_runner - } - - #[inline] - pub fn vs_code(&self) -> bool { - self.vs_code - } - - // Write the state file. - // The file's format is very simple: - // - The first line is a comment. - // - The second line is an empty line. - // - The third line is the name of the current exercise. It must end with `\n` even if there - // are no done exercises. - // - The fourth line is an empty line. - // - All remaining lines are the names of done exercises. - fn write(&mut self) -> Result<()> { - self.file_buf.truncate(STATE_FILE_HEADER.len()); - - self.file_buf - .extend_from_slice(self.current_exercise().name.as_bytes()); - self.file_buf.push(b'\n'); - - for exercise in &self.exercises { - if exercise.done { - self.file_buf.push(b'\n'); - self.file_buf.extend_from_slice(exercise.name.as_bytes()); - } - } - - self.state_file - .rewind() - .with_context(|| format!("Failed to rewind the state file {STATE_FILE_NAME}"))?; - self.state_file - .set_len(0) - .with_context(|| format!("Failed to truncate the state file {STATE_FILE_NAME}"))?; - self.state_file - .write_all(&self.file_buf) - .with_context(|| format!("Failed to write the state file {STATE_FILE_NAME}"))?; - - Ok(()) - } - - pub fn set_current_exercise_ind(&mut self, exercise_ind: usize) -> Result<()> { - if exercise_ind == self.current_exercise_ind { - return Ok(()); - } - - if exercise_ind >= self.exercises.len() { - bail!(BAD_INDEX_ERR); - } - - self.current_exercise_ind = exercise_ind; - - self.write() - } - - pub fn set_current_exercise_by_name(&mut self, name: &str) -> Result<()> { - // O(N) is fine since this method is used only once until the program exits. - // Building a hashmap would have more overhead. - self.current_exercise_ind = self - .exercises - .iter() - .position(|exercise| exercise.name == name) - .with_context(|| format!("No exercise found for '{name}'!"))?; - - self.write() - } - - // Set the status of an exercise without saving. Returns `true` if the - // status actually changed (and thus needs saving later). - pub fn set_status(&mut self, exercise_ind: usize, done: bool) -> Result { - let exercise = self - .exercises - .get_mut(exercise_ind) - .context(BAD_INDEX_ERR)?; - - if exercise.done == done { - return Ok(false); - } - - exercise.done = done; - if done { - self.n_done += 1; - } else { - self.n_done -= 1; - } - - Ok(true) - } - - // Set the status of an exercise to "pending" and save. - pub fn set_pending(&mut self, exercise_ind: usize) -> Result<()> { - if self.set_status(exercise_ind, false)? { - self.write()?; - } - - Ok(()) - } - - // Official exercises: Dump the original file from the binary. - // Community exercises: Reset the exercise file with `git stash`. - fn reset(&self, exercise_ind: usize, path: &str) -> Result<()> { - if self.official_exercises { - return EMBEDDED_FILES - .write_exercise_to_disk(exercise_ind, path) - .with_context(|| format!("Failed to reset the exercise {path}")); - } - - let output = Command::new("git") - .arg("stash") - .arg("push") - .arg("--") - .arg(path) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .output() - .with_context(|| format!("Failed to run `git stash push -- {path}`"))?; - - if !output.status.success() { - bail!( - "`git stash push -- {path}` didn't run successfully: {}", - String::from_utf8_lossy(&output.stderr), - ); - } - - Ok(()) - } - - pub fn reset_current_exercise(&mut self) -> Result<&'static str> { - self.set_pending(self.current_exercise_ind)?; - let exercise = self.current_exercise(); - self.reset(self.current_exercise_ind, exercise.path)?; - - Ok(exercise.path) - } - - // Reset the exercise by index and return its name. - pub fn reset_exercise_by_ind(&mut self, exercise_ind: usize) -> Result<&'static str> { - if exercise_ind >= self.exercises.len() { - bail!(BAD_INDEX_ERR); - } - - self.set_pending(exercise_ind)?; - let exercise = &self.exercises[exercise_ind]; - self.reset(exercise_ind, exercise.path)?; - - Ok(exercise.name) - } - - // Return the index of the next pending exercise or `None` if all exercises are done. - fn next_pending_exercise_ind(&self) -> Option { - let next_ind = self.current_exercise_ind + 1; - self.exercises - // If the exercise done isn't the last, search for pending exercises after it. - .get(next_ind..) - .and_then(|later_exercises| { - later_exercises - .iter() - .position(|exercise| !exercise.done) - .map(|ind| next_ind + ind) - }) - // Search from the start. - .or_else(|| { - self.exercises[..self.current_exercise_ind] - .iter() - .position(|exercise| !exercise.done) - }) - } - - /// Official exercises: Dump the solution file from the binary and return its path. - /// Community exercises: Check if a solution file exists and return its path in that case. - pub fn current_solution_path(&self) -> Result> { - if cfg!(debug_assertions) { - return Ok(None); - } - - let current_exercise = self.current_exercise(); - - if self.official_exercises { - EMBEDDED_FILES - .write_solution_to_disk(self.current_exercise_ind, current_exercise.name) - .map(Some) - } else { - let sol_path = current_exercise.sol_path(); - - if Path::new(&sol_path).exists() { - return Ok(Some(sol_path)); - } - - Ok(None) - } - } - - fn check_all_exercises_impl(&mut self, stdout: &mut StdoutLock) -> Result> { - let term_width = terminal::size() - .context("Failed to get the terminal size")? - .0; - let mut progress_visualizer = CheckProgressVisualizer::build(stdout, term_width)?; - - let next_exercise_ind = AtomicUsize::new(0); - let mut progresses = vec![CheckProgress::None; self.exercises.len()]; - - thread::scope(|s| { - let (exercise_progress_sender, exercise_progress_receiver) = mpsc::channel(); - let n_threads = thread::available_parallelism() - .map_or(DEFAULT_CHECK_PARALLELISM, |count| count.get()); - - for _ in 0..n_threads { - let exercise_progress_sender = exercise_progress_sender.clone(); - let next_exercise_ind = &next_exercise_ind; - let slf = &self; - thread::Builder::new() - .spawn_scoped(s, move || { - loop { - let exercise_ind = next_exercise_ind.fetch_add(1, Relaxed); - let Some(exercise) = slf.exercises.get(exercise_ind) else { - // No more exercises. - break; - }; - - if exercise_progress_sender - .send((exercise_ind, CheckProgress::Checking)) - .is_err() - { - break; - }; - - let success = exercise.run_exercise(None, &slf.cmd_runner); - let progress = match success { - Ok(true) => CheckProgress::Done, - Ok(false) => CheckProgress::Pending, - Err(_) => CheckProgress::None, - }; - - if exercise_progress_sender - .send((exercise_ind, progress)) - .is_err() - { - break; - } - } - }) - .context("Failed to spawn a thread to check all exercises")?; - } - - // Drop this sender to detect when the last thread is done. - drop(exercise_progress_sender); - - while let Ok((exercise_ind, progress)) = exercise_progress_receiver.recv() { - progresses[exercise_ind] = progress; - progress_visualizer.update(&progresses)?; - } - - Ok::<_, Error>(()) - })?; - - let mut first_pending_exercise_ind = None; - for exercise_ind in 0..progresses.len() { - match progresses[exercise_ind] { - CheckProgress::Done => { - self.set_status(exercise_ind, true)?; - } - CheckProgress::Pending => { - self.set_status(exercise_ind, false)?; - if first_pending_exercise_ind.is_none() { - first_pending_exercise_ind = Some(exercise_ind); - } - } - CheckProgress::None | CheckProgress::Checking => { - // If we got an error while checking all exercises in parallel, - // it could be because we exceeded the limit of open file descriptors. - // Therefore, try running exercises with errors sequentially. - progresses[exercise_ind] = CheckProgress::Checking; - progress_visualizer.update(&progresses)?; - - let exercise = &self.exercises[exercise_ind]; - let success = exercise.run_exercise(None, &self.cmd_runner)?; - if success { - progresses[exercise_ind] = CheckProgress::Done; - } else { - progresses[exercise_ind] = CheckProgress::Pending; - if first_pending_exercise_ind.is_none() { - first_pending_exercise_ind = Some(exercise_ind); - } - } - self.set_status(exercise_ind, success)?; - progress_visualizer.update(&progresses)?; - } - } - } - - self.write()?; - - Ok(first_pending_exercise_ind) - } - - // Return the exercise index of the first pending exercise found. - pub fn check_all_exercises(&mut self, stdout: &mut StdoutLock) -> Result> { - stdout.queue(cursor::Hide)?; - let res = self.check_all_exercises_impl(stdout); - stdout.queue(cursor::Show)?; - - res - } - - /// Mark the current exercise as done and move on to the next pending exercise if one exists. - /// If all exercises are marked as done, run all of them to make sure that they are actually - /// done. If an exercise which is marked as done fails, mark it as pending and continue on it. - pub fn done_current_exercise( - &mut self, - stdout: &mut StdoutLock, - ) -> Result { - let exercise = &mut self.exercises[self.current_exercise_ind]; - if !exercise.done { - exercise.done = true; - self.n_done += 1; - } - - if let Some(ind) = self.next_pending_exercise_ind() { - self.set_current_exercise_ind(ind)?; - return Ok(ExercisesProgress::NewPending); - } - - if CLEAR_BEFORE_FINAL_CHECK { - clear_terminal(stdout)?; - } else { - stdout.write_all(b"\n")?; - } - - if let Some(first_pending_exercise_ind) = self.check_all_exercises(stdout)? { - self.set_current_exercise_ind(first_pending_exercise_ind)?; - - return Ok(ExercisesProgress::NewPending); - } - - self.render_final_message(stdout)?; - - Ok(ExercisesProgress::AllDone) - } - - pub fn render_final_message(&self, stdout: &mut StdoutLock) -> Result<()> { - clear_terminal(stdout)?; - stdout.write_all(FENISH_LINE.as_bytes())?; - - let final_message = self.final_message.trim_ascii(); - if !final_message.is_empty() { - stdout.write_all(final_message.as_bytes())?; - stdout.write_all(b"\n")?; - } - - Ok(()) - } -} - -const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises"; -const STATE_FILE_HEADER: &[u8] = b"DON'T EDIT THIS FILE!\n\n"; -const FENISH_LINE: &str = "+----------------------------------------------------+ -| You made it to the Fe-nish line! | -+-------------------------- ------------------------+ - \\/\x1b[31m - ▒▒ ▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒ ▒▒ - ▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒ - ▒▒▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒▒▒ - ░░▒▒▒▒░░▒▒ ▒▒ ▒▒ ▒▒ ▒▒░░▒▒▒▒ - ▓▓▓▓▓▓▓▓ ▓▓ ▓▓██ ▓▓ ▓▓██ ▓▓ ▓▓▓▓▓▓▓▓ - ▒▒▒▒ ▒▒ ████ ▒▒ ████ ▒▒░░ ▒▒▒▒ - ▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒ - ▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒ - ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - ▒▒▒▒▒▒▒▒▒▒██▒▒▒▒▒▒██▒▒▒▒▒▒▒▒▒▒ - ▒▒ ▒▒▒▒▒▒▒▒▒▒██████▒▒▒▒▒▒▒▒▒▒ ▒▒ - ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ - ▒▒ ▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒ ▒▒ - ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ ▒▒ - ▒▒ ▒▒ ▒▒ ▒▒\x1b[0m - -"; - -#[cfg(test)] -mod tests { - use super::*; - - fn dummy_exercise() -> Exercise { - Exercise { - dir: None, - name: "0", - path: "exercises/0.rs", - canonical_path: None, - test: false, - strict_clippy: false, - hint: "", - done: false, - } - } - - #[test] - fn next_pending_exercise() { - let mut app_state = AppState { - current_exercise_ind: 0, - exercises: vec![dummy_exercise(), dummy_exercise(), dummy_exercise()], - n_done: 0, - final_message: String::new(), - state_file: tempfile::tempfile().unwrap(), - file_buf: Vec::new(), - official_exercises: true, - cmd_runner: CmdRunner::build().unwrap(), - vs_code: false, - }; - - let mut assert = |done: [bool; 3], expected: [Option; 3]| { - for (exercise, done) in app_state.exercises.iter_mut().zip(done) { - exercise.done = done; - } - for (ind, expected) in expected.into_iter().enumerate() { - app_state.current_exercise_ind = ind; - assert_eq!( - app_state.next_pending_exercise_ind(), - expected, - "done={done:?}, ind={ind}", - ); - } - }; - - assert([true, true, true], [None, None, None]); - assert([false, false, false], [Some(1), Some(2), Some(0)]); - assert([false, true, true], [None, Some(0), Some(0)]); - assert([true, false, true], [Some(1), None, Some(1)]); - assert([true, true, false], [Some(2), Some(2), None]); - assert([true, false, false], [Some(1), Some(2), Some(1)]); - assert([false, true, false], [Some(2), Some(2), Some(0)]); - assert([false, false, true], [Some(1), Some(0), Some(0)]); - } -} diff --git a/src/cargo_toml.rs b/src/cargo_toml.rs deleted file mode 100644 index ce0dfd0cf3..0000000000 --- a/src/cargo_toml.rs +++ /dev/null @@ -1,153 +0,0 @@ -use anyhow::{Context, Result}; -use std::path::Path; - -use crate::{exercise::RunnableExercise, info_file::ExerciseInfo}; - -/// Initial capacity of the bins buffer. -pub const BINS_BUFFER_CAPACITY: usize = 1 << 14; - -/// Return the start and end index of the content of the list `bin = […]`. -/// bin = [xxxxxxxxxxxxxxxxx] -/// |start_ind | -/// |end_ind -pub fn bins_start_end_ind(cargo_toml: &str) -> Result<(usize, usize)> { - let start_ind = cargo_toml - .find("bin = [") - .context("Failed to find the start of the `bin` list (`bin = [`)")? - + 7; - let end_ind = start_ind - + cargo_toml - .get(start_ind..) - .and_then(|slice| slice.as_bytes().iter().position(|c| *c == b']')) - .context("Failed to find the end of the `bin` list (`]`)")?; - - Ok((start_ind, end_ind)) -} - -/// Generate and append the content of the `bin` list in `Cargo.toml`. -/// The `exercise_path_prefix` is the prefix of the `path` field of every list entry. -pub fn append_bins( - buf: &mut Vec, - exercise_infos: &[ExerciseInfo], - exercise_path_prefix: &[u8], -) { - buf.push(b'\n'); - for exercise_info in exercise_infos { - buf.extend_from_slice(b" { name = \""); - buf.extend_from_slice(exercise_info.name.as_bytes()); - buf.extend_from_slice(b"\", path = \""); - buf.extend_from_slice(exercise_path_prefix); - buf.extend_from_slice(b"exercises/"); - if let Some(dir) = &exercise_info.dir { - buf.extend_from_slice(dir.as_bytes()); - buf.push(b'/'); - } - buf.extend_from_slice(exercise_info.name.as_bytes()); - buf.extend_from_slice(b".rs\" },\n"); - - let sol_path = exercise_info.sol_path(); - if !Path::new(&sol_path).exists() { - continue; - } - - buf.extend_from_slice(b" { name = \""); - buf.extend_from_slice(exercise_info.name.as_bytes()); - buf.extend_from_slice(b"_sol"); - buf.extend_from_slice(b"\", path = \""); - buf.extend_from_slice(exercise_path_prefix); - buf.extend_from_slice(b"solutions/"); - if let Some(dir) = &exercise_info.dir { - buf.extend_from_slice(dir.as_bytes()); - buf.push(b'/'); - } - buf.extend_from_slice(exercise_info.name.as_bytes()); - buf.extend_from_slice(b".rs\" },\n"); - } -} - -/// Update the `bin` list and leave everything else unchanged. -pub fn updated_cargo_toml( - exercise_infos: &[ExerciseInfo], - current_cargo_toml: &str, - exercise_path_prefix: &[u8], -) -> Result> { - let (bins_start_ind, bins_end_ind) = bins_start_end_ind(current_cargo_toml)?; - - let mut updated_cargo_toml = Vec::with_capacity(BINS_BUFFER_CAPACITY); - updated_cargo_toml.extend_from_slice(¤t_cargo_toml.as_bytes()[..bins_start_ind]); - append_bins( - &mut updated_cargo_toml, - exercise_infos, - exercise_path_prefix, - ); - updated_cargo_toml.extend_from_slice(¤t_cargo_toml.as_bytes()[bins_end_ind..]); - - Ok(updated_cargo_toml) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_bins_start_end_ind() { - assert_eq!(bins_start_end_ind("").ok(), None); - assert_eq!(bins_start_end_ind("[]").ok(), None); - assert_eq!(bins_start_end_ind("bin = [").ok(), None); - assert_eq!(bins_start_end_ind("bin = ]").ok(), None); - assert_eq!(bins_start_end_ind("bin = []").ok(), Some((7, 7))); - assert_eq!(bins_start_end_ind("bin= []").ok(), None); - assert_eq!(bins_start_end_ind("bin =[]").ok(), None); - assert_eq!(bins_start_end_ind("bin=[]").ok(), None); - assert_eq!(bins_start_end_ind("bin = [\nxxx\n]").ok(), Some((7, 12))); - } - - #[test] - fn test_bins() { - let exercise_infos = [ - ExerciseInfo { - name: String::from("1"), - dir: None, - test: true, - strict_clippy: true, - hint: String::new(), - skip_check_unsolved: false, - }, - ExerciseInfo { - name: String::from("2"), - dir: Some(String::from("d")), - test: false, - strict_clippy: false, - hint: String::new(), - skip_check_unsolved: false, - }, - ]; - - let mut buf = Vec::with_capacity(128); - append_bins(&mut buf, &exercise_infos, b""); - assert_eq!( - buf, - br#" - { name = "1", path = "exercises/1.rs" }, - { name = "2", path = "exercises/d/2.rs" }, -"#, - ); - - assert_eq!( - updated_cargo_toml( - &exercise_infos, - "abc\n\ - bin = [xxx]\n\ - 123", - b"../" - ) - .unwrap(), - br#"abc -bin = [ - { name = "1", path = "../exercises/1.rs" }, - { name = "2", path = "../exercises/d/2.rs" }, -] -123"#, - ); - } -} diff --git a/src/cmd.rs b/src/cmd.rs deleted file mode 100644 index b2c58f6abb..0000000000 --- a/src/cmd.rs +++ /dev/null @@ -1,163 +0,0 @@ -use anyhow::{Context, Result, bail}; -use serde::Deserialize; -use std::{ - io::{Read, pipe}, - path::PathBuf, - process::{Command, Stdio}, -}; - -/// Run a command with a description for a possible error and append the merged stdout and stderr. -/// The boolean in the returned `Result` is true if the command's exit status is success. -fn run_cmd(mut cmd: Command, description: &str, output: Option<&mut Vec>) -> Result { - let spawn = |mut cmd: Command| { - // NOTE: The closure drops `cmd` which prevents a pipe deadlock. - cmd.stdin(Stdio::null()) - .spawn() - .with_context(|| format!("Failed to run the command `{description}`")) - }; - - let mut handle = if let Some(output) = output { - let (mut reader, writer) = pipe().with_context(|| { - format!("Failed to create a pipe to run the command `{description}``") - })?; - - let writer_clone = writer.try_clone().with_context(|| { - format!("Failed to clone the pipe writer for the command `{description}`") - })?; - - cmd.stdout(writer_clone).stderr(writer); - let handle = spawn(cmd)?; - - reader - .read_to_end(output) - .with_context(|| format!("Failed to read the output of the command `{description}`"))?; - - output.push(b'\n'); - - handle - } else { - cmd.stdout(Stdio::null()).stderr(Stdio::null()); - spawn(cmd)? - }; - - handle - .wait() - .with_context(|| format!("Failed to wait on the command `{description}` to exit")) - .map(|status| status.success()) -} - -// Parses parts of the output of `cargo metadata`. -#[derive(Deserialize)] -struct CargoMetadata { - target_directory: PathBuf, -} - -pub struct CmdRunner { - target_dir: PathBuf, -} - -impl CmdRunner { - pub fn build() -> Result { - // Get the target directory from Cargo. - let metadata_output = Command::new("cargo") - .arg("metadata") - .arg("-q") - .arg("--format-version") - .arg("1") - .arg("--no-deps") - .stdin(Stdio::null()) - .stderr(Stdio::inherit()) - .output() - .context(CARGO_METADATA_ERR)?; - - if !metadata_output.status.success() { - bail!("The command `cargo metadata …` failed. Are you in the `rustlings/` directory?"); - } - - let metadata: CargoMetadata = serde_json::de::from_slice(&metadata_output.stdout) - .context( - "Failed to read the field `target_directory` from the output of the command `cargo metadata …`", - )?; - - Ok(Self { - target_dir: metadata.target_directory, - }) - } - - pub fn cargo<'out>( - &self, - subcommand: &str, - bin_name: &str, - output: Option<&'out mut Vec>, - ) -> CargoSubcommand<'out> { - let mut cmd = Command::new("cargo"); - cmd.arg(subcommand).arg("-q").arg("--bin").arg(bin_name); - - // A hack to make `cargo run` work when developing Rustlings. - #[cfg(debug_assertions)] - cmd.arg("--manifest-path") - .arg("dev/Cargo.toml") - .arg("--target-dir") - .arg(&self.target_dir); - - if output.is_some() { - cmd.arg("--color").arg("always"); - } - - CargoSubcommand { cmd, output } - } - - /// The boolean in the returned `Result` is true if the command's exit status is success. - pub fn run_debug_bin(&self, bin_name: &str, output: Option<&mut Vec>) -> Result { - // 7 = "/debug/".len() - let mut bin_path = - PathBuf::with_capacity(self.target_dir.as_os_str().len() + 7 + bin_name.len()); - bin_path.push(&self.target_dir); - bin_path.push("debug"); - bin_path.push(bin_name); - - run_cmd(Command::new(&bin_path), &bin_path.to_string_lossy(), output) - } -} - -pub struct CargoSubcommand<'out> { - cmd: Command, - output: Option<&'out mut Vec>, -} - -impl CargoSubcommand<'_> { - #[inline] - pub fn args<'arg, I>(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - { - self.cmd.args(args); - self - } - - /// The boolean in the returned `Result` is true if the command's exit status is success. - #[inline] - pub fn run(self, description: &str) -> Result { - run_cmd(self.cmd, description, self.output) - } -} - -const CARGO_METADATA_ERR: &str = "Failed to run the command `cargo metadata …` -Did you already install Rust? -Try running `cargo --version` to diagnose the problem."; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_run_cmd() { - let mut cmd = Command::new("echo"); - cmd.arg("Hello"); - - let mut output = Vec::with_capacity(8); - run_cmd(cmd, "echo …", Some(&mut output)).unwrap(); - - assert_eq!(output, b"Hello\n\n"); - } -} diff --git a/src/dev.rs b/src/dev.rs deleted file mode 100644 index 41fddbeb98..0000000000 --- a/src/dev.rs +++ /dev/null @@ -1,46 +0,0 @@ -use anyhow::{Context, Result, bail}; -use clap::Subcommand; -use std::path::PathBuf; - -mod check; -mod new; -mod update; - -#[derive(Subcommand)] -pub enum DevCommands { - /// Create a new project for community exercises - New { - /// The path to create the project in - path: PathBuf, - /// Don't try to initialize a Git repository in the project directory - #[arg(long)] - no_git: bool, - }, - /// Run checks on the exercises - Check { - /// Require that every exercise has a solution - #[arg(short, long)] - require_solutions: bool, - }, - /// Update the `Cargo.toml` file for the exercises - Update, -} - -impl DevCommands { - pub fn run(self) -> Result<()> { - match self { - Self::New { path, no_git } => { - if cfg!(debug_assertions) { - bail!("Disabled in the debug build"); - } - - new::new(&path, no_git).context(INIT_ERR) - } - Self::Check { require_solutions } => check::check(require_solutions), - Self::Update => update::update(), - } - } -} - -const INIT_ERR: &str = "Initialization failed. -After resolving the issue, delete the `rustlings` directory (if it was created) and try again"; diff --git a/src/dev/check.rs b/src/dev/check.rs deleted file mode 100644 index f71110630d..0000000000 --- a/src/dev/check.rs +++ /dev/null @@ -1,398 +0,0 @@ -use anyhow::{Context, Error, Result, anyhow, bail}; -use std::{ - cmp::Ordering, - collections::HashSet, - fs::{self, OpenOptions, read_dir}, - io::{self, Read, Write}, - path::{Path, PathBuf}, - process::{Command, Stdio}, - thread, -}; - -use crate::{ - CURRENT_FORMAT_VERSION, - cargo_toml::{BINS_BUFFER_CAPACITY, append_bins, bins_start_end_ind}, - cmd::CmdRunner, - exercise::{OUTPUT_CAPACITY, RunnableExercise}, - info_file::{ExerciseInfo, InfoFile}, - term::ProgressCounter, -}; - -const MAX_N_EXERCISES: usize = 999; -const MAX_EXERCISE_NAME_LEN: usize = 32; - -// Find a char that isn't allowed in the exercise's `name` or `dir`. -fn forbidden_char(input: &str) -> Option { - input.chars().find(|c| !c.is_alphanumeric() && *c != '_') -} - -// Check that the `Cargo.toml` file is up-to-date. -fn check_cargo_toml( - exercise_infos: &[ExerciseInfo], - cargo_toml_path: &str, - exercise_path_prefix: &[u8], -) -> Result<()> { - let current_cargo_toml = fs::read_to_string(cargo_toml_path) - .with_context(|| format!("Failed to read the file `{cargo_toml_path}`"))?; - - let (bins_start_ind, bins_end_ind) = bins_start_end_ind(¤t_cargo_toml)?; - - let old_bins = ¤t_cargo_toml.as_bytes()[bins_start_ind..bins_end_ind]; - let mut new_bins = Vec::with_capacity(BINS_BUFFER_CAPACITY); - append_bins(&mut new_bins, exercise_infos, exercise_path_prefix); - - if old_bins != new_bins { - if cfg!(debug_assertions) { - bail!( - "The file `dev/Cargo.toml` is outdated. Run `cargo dev update` to update it. Then run `cargo run -- dev check` again" - ); - } - - bail!( - "The file `Cargo.toml` is outdated. Run `rustlings dev update` to update it. Then run `rustlings dev check` again" - ); - } - - Ok(()) -} - -// Check the info of all exercises and return their paths in a set. -fn check_info_file_exercises(info_file: &InfoFile) -> Result> { - let mut names = HashSet::with_capacity(info_file.exercises.len()); - let mut paths = HashSet::with_capacity(info_file.exercises.len()); - - let mut file_buf = String::with_capacity(1 << 14); - for exercise_info in &info_file.exercises { - let name = exercise_info.name.as_str(); - if name.is_empty() { - bail!("Found an empty exercise name in `info.toml`"); - } - if name.len() > MAX_EXERCISE_NAME_LEN { - bail!( - "The length of the exercise name `{name}` is bigger than the maximum {MAX_EXERCISE_NAME_LEN}" - ); - } - if let Some(c) = forbidden_char(name) { - bail!("Char `{c}` in the exercise name `{name}` is not allowed"); - } - - if let Some(dir) = &exercise_info.dir { - if dir.is_empty() { - bail!("The exercise `{name}` has an empty dir name in `info.toml`"); - } - if let Some(c) = forbidden_char(dir) { - bail!("Char `{c}` in the exercise dir `{dir}` is not allowed"); - } - } - - if exercise_info.hint.trim_ascii().is_empty() { - bail!( - "The exercise `{name}` has an empty hint. Please provide a hint or at least tell the user why a hint isn't needed for this exercise" - ); - } - - if !names.insert(name) { - bail!("The exercise name `{name}` is duplicated. Exercise names must all be unique"); - } - - let path = exercise_info.path(); - - OpenOptions::new() - .read(true) - .open(&path) - .with_context(|| format!("Failed to open the file {path}"))? - .read_to_string(&mut file_buf) - .with_context(|| format!("Failed to read the file {path}"))?; - - if !file_buf.contains("fn main()") { - bail!( - "The `main` function is missing in the file `{path}`.\n\ - Create at least an empty `main` function to avoid language server errors" - ); - } - - if !file_buf.contains("// TODO") { - bail!( - "Didn't find any `// TODO` comment in the file `{path}`.\n\ - You need to have at least one such comment to guide the user." - ); - } - - let contains_tests = file_buf.contains("#[test]\n"); - if exercise_info.test { - if !contains_tests { - bail!( - "The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file" - ); - } - } else if contains_tests { - bail!( - "The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file" - ); - } - - file_buf.clear(); - - paths.insert(PathBuf::from(path)); - } - - Ok(paths) -} - -// Check `dir` for unexpected files. -// Only Rust files in `allowed_rust_files` and `README.md` files are allowed. -// Only one level of directory nesting is allowed. -fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet) -> Result<()> { - let unexpected_file = |path: &Path| { - anyhow!( - "Found the file `{}`. Only `README.md` and Rust files related to an exercise in `info.toml` are allowed in the `{dir}` directory", - path.display() - ) - }; - - for entry in read_dir(dir).with_context(|| format!("Failed to open the `{dir}` directory"))? { - let entry = entry.with_context(|| format!("Failed to read the `{dir}` directory"))?; - - if entry.file_type().unwrap().is_file() { - let path = entry.path(); - let file_name = path.file_name().unwrap(); - if file_name == "README.md" { - continue; - } - - if !allowed_rust_files.contains(&path) { - return Err(unexpected_file(&path)); - } - - continue; - } - - let dir_path = entry.path(); - for entry in read_dir(&dir_path) - .with_context(|| format!("Failed to open the directory {}", dir_path.display()))? - { - let entry = entry - .with_context(|| format!("Failed to read the directory {}", dir_path.display()))?; - let path = entry.path(); - - if !entry.file_type().unwrap().is_file() { - bail!( - "Found `{}` but expected only files. Only one level of exercise nesting is allowed", - path.display() - ); - } - - let file_name = path.file_name().unwrap(); - if file_name == "README.md" { - continue; - } - - if !allowed_rust_files.contains(&path) { - return Err(unexpected_file(&path)); - } - } - } - - Ok(()) -} - -fn check_exercises_unsolved( - info_file: &'static InfoFile, - cmd_runner: &'static CmdRunner, -) -> Result<()> { - let mut stdout = io::stdout().lock(); - stdout.write_all(b"Running all exercises to check that they aren't already solved...\n")?; - - let handles = info_file - .exercises - .iter() - .filter_map(|exercise_info| { - if exercise_info.skip_check_unsolved { - return None; - } - - Some( - thread::Builder::new() - .spawn(|| exercise_info.run_exercise(None, cmd_runner)) - .map(|handle| (exercise_info.name.as_str(), handle)), - ) - }) - .collect::, _>>() - .context("Failed to spawn a thread to check if an exercise is already solved")?; - - let mut progress_counter = ProgressCounter::new(&mut stdout, handles.len())?; - - for (exercise_name, handle) in handles { - let Ok(result) = handle.join() else { - bail!("Panic while trying to run the exercise {exercise_name}"); - }; - - match result { - Ok(true) => { - bail!( - "The exercise {exercise_name} is already solved.\n\ - {SKIP_CHECK_UNSOLVED_HINT}", - ) - } - Ok(false) => (), - Err(e) => return Err(e), - } - - progress_counter.increment()?; - } - - Ok(()) -} - -fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> { - match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) { - Ordering::Less => bail!( - "`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\n\ - Please migrate to the latest format version" - ), - Ordering::Greater => bail!( - "`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\n\ - Try updating the Rustlings program" - ), - Ordering::Equal => (), - } - - let handle = thread::Builder::new() - .spawn(move || check_exercises_unsolved(info_file, cmd_runner)) - .context("Failed to spawn a thread to check if any exercise is already solved")?; - - let info_file_paths = check_info_file_exercises(info_file)?; - check_unexpected_files("exercises", &info_file_paths)?; - - handle.join().unwrap() -} - -enum SolutionCheck { - Success { sol_path: String }, - MissingOptional, - RunFailure { output: Vec }, - Err(Error), -} - -fn check_solutions( - require_solutions: bool, - info_file: &'static InfoFile, - cmd_runner: &'static CmdRunner, -) -> Result<()> { - let mut stdout = io::stdout().lock(); - stdout.write_all(b"Running all solutions...\n")?; - - let handles = info_file - .exercises - .iter() - .map(|exercise_info| { - thread::Builder::new().spawn(move || { - let sol_path = exercise_info.sol_path(); - if !Path::new(&sol_path).exists() { - if require_solutions { - return SolutionCheck::Err(anyhow!( - "The solution of the exercise {} is missing", - exercise_info.name, - )); - } - - return SolutionCheck::MissingOptional; - } - - let mut output = Vec::with_capacity(OUTPUT_CAPACITY); - match exercise_info.run_solution(Some(&mut output), cmd_runner) { - Ok(true) => SolutionCheck::Success { sol_path }, - Ok(false) => SolutionCheck::RunFailure { output }, - Err(e) => SolutionCheck::Err(e), - } - }) - }) - .collect::, _>>() - .context("Failed to spawn a thread to check a solution")?; - - let mut sol_paths = HashSet::with_capacity(info_file.exercises.len()); - let mut fmt_cmd = Command::new("rustfmt"); - fmt_cmd - .arg("--check") - .arg("--edition") - .arg("2024") - .arg("--color") - .arg("always") - .stdin(Stdio::null()); - - let mut progress_counter = ProgressCounter::new(&mut stdout, handles.len())?; - - for (exercise_info, handle) in info_file.exercises.iter().zip(handles) { - let Ok(check_result) = handle.join() else { - bail!( - "Panic while trying to run the solution of the exercise {}", - exercise_info.name, - ); - }; - - match check_result { - SolutionCheck::Success { sol_path } => { - fmt_cmd.arg(&sol_path); - sol_paths.insert(PathBuf::from(sol_path)); - } - SolutionCheck::MissingOptional => (), - SolutionCheck::RunFailure { output } => { - drop(progress_counter); - stdout.write_all(&output)?; - bail!( - "Running the solution of the exercise {} failed with the error above", - exercise_info.name, - ); - } - SolutionCheck::Err(e) => return Err(e), - } - - progress_counter.increment()?; - } - - let n_solutions = sol_paths.len(); - let handle = thread::Builder::new() - .spawn(move || check_unexpected_files("solutions", &sol_paths)) - .context( - "Failed to spawn a thread to check for unexpected files in the solutions directory", - )?; - - if n_solutions > 0 - && !fmt_cmd - .status() - .context("Failed to run `rustfmt` on all solution files")? - .success() - { - bail!("Some solutions aren't formatted. Run `rustfmt` on them"); - } - - handle.join().unwrap() -} - -pub fn check(require_solutions: bool) -> Result<()> { - let info_file = InfoFile::parse()?; - - if info_file.exercises.len() > MAX_N_EXERCISES { - bail!("The maximum number of exercises is {MAX_N_EXERCISES}"); - } - - if cfg!(debug_assertions) { - // A hack to make `cargo dev check` work when developing Rustlings. - check_cargo_toml(&info_file.exercises, "dev/Cargo.toml", b"../")?; - } else { - check_cargo_toml(&info_file.exercises, "Cargo.toml", b"")?; - } - - // Leaking is fine since they are used until the end of the program. - let cmd_runner = Box::leak(Box::new(CmdRunner::build()?)); - let info_file = Box::leak(Box::new(info_file)); - - check_exercises(info_file, cmd_runner)?; - check_solutions(require_solutions, info_file, cmd_runner)?; - - println!("Everything looks fine!"); - - Ok(()) -} - -const SKIP_CHECK_UNSOLVED_HINT: &str = "If this is an introduction exercise that is intended to be already solved, add `skip_check_unsolved = true` to the exercise's metadata in the `info.toml` file"; diff --git a/src/dev/new.rs b/src/dev/new.rs deleted file mode 100644 index 7c72a6b700..0000000000 --- a/src/dev/new.rs +++ /dev/null @@ -1,148 +0,0 @@ -use anyhow::{Context, Result, bail}; -use std::{ - env::set_current_dir, - fs::{self, create_dir}, - path::Path, - process::Command, -}; - -use crate::{CURRENT_FORMAT_VERSION, init::RUST_ANALYZER_TOML}; - -// Create a directory relative to the current directory and print its path. -fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> { - create_dir(dir_name) - .with_context(|| format!("Failed to create the directory {current_dir}/{dir_name}"))?; - println!("Created the directory {current_dir}/{dir_name}"); - Ok(()) -} - -// Write a file relative to the current directory and print its path. -fn write_rel_file(file_name: &str, current_dir: &str, content: C) -> Result<()> -where - C: AsRef<[u8]>, -{ - fs::write(file_name, content) - .with_context(|| format!("Failed to create the file {current_dir}/{file_name}"))?; - // Space to align with `create_rel_dir`. - println!("Created the file {current_dir}/{file_name}"); - Ok(()) -} - -pub fn new(path: &Path, no_git: bool) -> Result<()> { - let dir_path_str = path.to_string_lossy(); - - create_dir(path).with_context(|| format!("Failed to create the directory {dir_path_str}"))?; - println!("Created the directory {dir_path_str}"); - - set_current_dir(path) - .with_context(|| format!("Failed to set {dir_path_str} as the current directory"))?; - - if !no_git - && !Command::new("git") - .arg("init") - .status() - .context("Failed to run `git init`")? - .success() - { - bail!("`git init` didn't run successfully. See the possible error message above"); - } - - write_rel_file(".gitignore", &dir_path_str, GITIGNORE)?; - - create_rel_dir("exercises", &dir_path_str)?; - create_rel_dir("solutions", &dir_path_str)?; - - write_rel_file( - "info.toml", - &dir_path_str, - format!( - "{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}" - ), - )?; - - write_rel_file("Cargo.toml", &dir_path_str, CARGO_TOML)?; - - write_rel_file("README.md", &dir_path_str, README)?; - - write_rel_file("rust-analyzer.toml", &dir_path_str, RUST_ANALYZER_TOML)?; - - create_rel_dir(".vscode", &dir_path_str)?; - write_rel_file( - ".vscode/extensions.json", - &dir_path_str, - crate::init::VS_CODE_EXTENSIONS_JSON, - )?; - - println!("\nInitialization done ✓"); - - Ok(()) -} - -pub const GITIGNORE: &[u8] = b"Cargo.lock -target/ -.vscode/ -!.vscode/extensions.json -"; - -const INFO_FILE_BEFORE_FORMAT_VERSION: &str = - "# The format version is an indicator of the compatibility of community exercises with the -# Rustlings program. -# The format version is not the same as the version of the Rustlings program. -# In case Rustlings makes an unavoidable breaking change to the expected format of community -# exercises, you would need to raise this version and adapt to the new format. -# Otherwise, the newest version of the Rustlings program won't be able to run these exercises. -format_version = "; - -const INFO_FILE_AFTER_FORMAT_VERSION: &str = r#" - -# Optional multi-line message to be shown to users when just starting with the exercises. -welcome_message = """Welcome to these community Rustlings exercises.""" - -# Optional multi-line message to be shown to users after finishing all exercises. -final_message = """We hope that you found the exercises helpful :D""" - -# Repeat this section for every exercise. -[[exercises]] -# Exercise name which is the exercise file name without the `.rs` extension. -name = "???" - -# Optional directory name to be provided if you want to organize exercises in directories. -# If `dir` is specified, the exercise path is `exercises/DIR/NAME.rs` -# Otherwise, the path is `exercises/NAME.rs` -# dir = "???" - -# Rustlings expects the exercise to contain tests and run them. -# You can optionally disable testing by setting `test` to `false` (the default is `true`). -# In that case, the exercise will be considered done when it just successfully compiles. -# test = true - -# Rustlings will always run Clippy on exercises. -# You can optionally set `strict_clippy` to `true` (the default is `false`) to only consider -# the exercise as done when there are no warnings left. -# strict_clippy = false - -# A multi-line hint to be shown to users on request. -hint = """???""" -"#; - -const CARGO_TOML: &[u8] = - br#"# Don't edit the `bin` list manually! It is updated by `rustlings dev update` -bin = [] - -[package] -name = "exercises" -edition = "2024" -# Don't publish the exercises on crates.io! -publish = false - -[dependencies] -"#; - -const README: &str = "# Rustlings 🦀 - -Welcome to these community Rustlings exercises 😃 - -First, [install Rustlings using the official instructions](https://github.com/rust-lang/rustlings) ✅ - -Then, clone this repository, open a terminal in this directory and run `rustlings` to get started with the exercises 🚀 -"; diff --git a/src/dev/update.rs b/src/dev/update.rs deleted file mode 100644 index e0855a0e93..0000000000 --- a/src/dev/update.rs +++ /dev/null @@ -1,44 +0,0 @@ -use anyhow::{Context, Result}; -use std::fs; - -use crate::{ - cargo_toml::updated_cargo_toml, - info_file::{ExerciseInfo, InfoFile}, -}; - -// Update the `Cargo.toml` file. -fn update_cargo_toml( - exercise_infos: &[ExerciseInfo], - cargo_toml_path: &str, - exercise_path_prefix: &[u8], -) -> Result<()> { - let current_cargo_toml = fs::read_to_string(cargo_toml_path) - .with_context(|| format!("Failed to read the file `{cargo_toml_path}`"))?; - - let updated_cargo_toml = - updated_cargo_toml(exercise_infos, ¤t_cargo_toml, exercise_path_prefix)?; - - fs::write(cargo_toml_path, updated_cargo_toml) - .context("Failed to write the `Cargo.toml` file")?; - - Ok(()) -} - -pub fn update() -> Result<()> { - let info_file = InfoFile::parse()?; - - if cfg!(debug_assertions) { - // A hack to make `cargo dev update` work when developing Rustlings. - update_cargo_toml(&info_file.exercises, "dev/Cargo.toml", b"../") - .context("Failed to update the file `dev/Cargo.toml`")?; - - println!("Updated `dev/Cargo.toml`"); - } else { - update_cargo_toml(&info_file.exercises, "Cargo.toml", &[]) - .context("Failed to update the file `Cargo.toml`")?; - - println!("Updated `Cargo.toml`"); - } - - Ok(()) -} diff --git a/src/embedded.rs b/src/embedded.rs deleted file mode 100644 index 51a14b6a8d..0000000000 --- a/src/embedded.rs +++ /dev/null @@ -1,168 +0,0 @@ -use anyhow::{Context, Error, Result}; -use std::{ - fs::{self, create_dir}, - io, -}; - -use crate::info_file::ExerciseInfo; - -/// Contains all embedded files. -pub static EMBEDDED_FILES: EmbeddedFiles = rustlings_macros::include_files!(); - -// Files related to one exercise. -struct ExerciseFiles { - // The content of the exercise file. - exercise: &'static [u8], - // The content of the solution file. - solution: &'static [u8], - // Index of the related `ExerciseDir` in `EmbeddedFiles::exercise_dirs`. - dir_ind: usize, -} - -fn create_dir_if_not_exists(path: &str) -> Result<()> { - if let Err(e) = create_dir(path) { - if e.kind() != io::ErrorKind::AlreadyExists { - return Err(Error::from(e).context(format!("Failed to create the directory {path}"))); - } - } - - Ok(()) -} - -// A directory in the `exercises/` directory. -pub struct ExerciseDir { - pub name: &'static str, - readme: &'static [u8], -} - -impl ExerciseDir { - fn init_on_disk(&self) -> Result<()> { - // 20 = 10 + 10 - // exercises/ + /README.md - let mut dir_path = String::with_capacity(20 + self.name.len()); - dir_path.push_str("exercises/"); - dir_path.push_str(self.name); - create_dir_if_not_exists(&dir_path)?; - - let mut readme_path = dir_path; - readme_path.push_str("/README.md"); - - fs::write(&readme_path, self.readme) - .with_context(|| format!("Failed to write the file {readme_path}")) - } -} - -/// All embedded files. -pub struct EmbeddedFiles { - /// The content of the `info.toml` file. - pub info_file: &'static str, - exercise_files: &'static [ExerciseFiles], - pub exercise_dirs: &'static [ExerciseDir], -} - -impl EmbeddedFiles { - /// Dump all the embedded files of the `exercises/` directory. - pub fn init_exercises_dir(&self, exercise_infos: &[ExerciseInfo]) -> Result<()> { - create_dir("exercises").context("Failed to create the directory `exercises`")?; - - fs::write( - "exercises/README.md", - include_bytes!("../exercises/README.md"), - ) - .context("Failed to write the file exercises/README.md")?; - - for dir in self.exercise_dirs { - dir.init_on_disk()?; - } - - let mut exercise_path = String::with_capacity(64); - let prefix = "exercises/"; - exercise_path.push_str(prefix); - - for (exercise_info, exercise_files) in exercise_infos.iter().zip(self.exercise_files) { - let dir = &self.exercise_dirs[exercise_files.dir_ind]; - - exercise_path.truncate(prefix.len()); - exercise_path.push_str(dir.name); - exercise_path.push('/'); - exercise_path.push_str(&exercise_info.name); - exercise_path.push_str(".rs"); - - fs::write(&exercise_path, exercise_files.exercise) - .with_context(|| format!("Failed to write the exercise file {exercise_path}"))?; - } - - Ok(()) - } - - pub fn write_exercise_to_disk(&self, exercise_ind: usize, path: &str) -> Result<()> { - let exercise_files = &self.exercise_files[exercise_ind]; - let dir = &self.exercise_dirs[exercise_files.dir_ind]; - - dir.init_on_disk()?; - fs::write(path, exercise_files.exercise) - .with_context(|| format!("Failed to write the exercise file {path}")) - } - - /// Write the solution file to disk and return its path. - pub fn write_solution_to_disk( - &self, - exercise_ind: usize, - exercise_name: &str, - ) -> Result { - create_dir_if_not_exists("solutions")?; - - let exercise_files = &self.exercise_files[exercise_ind]; - let dir = &self.exercise_dirs[exercise_files.dir_ind]; - - // 14 = 10 + 1 + 3 - // solutions/ + / + .rs - let mut dir_path = String::with_capacity(14 + dir.name.len() + exercise_name.len()); - dir_path.push_str("solutions/"); - dir_path.push_str(dir.name); - create_dir_if_not_exists(&dir_path)?; - - let mut solution_path = dir_path; - solution_path.push('/'); - solution_path.push_str(exercise_name); - solution_path.push_str(".rs"); - - fs::write(&solution_path, exercise_files.solution) - .with_context(|| format!("Failed to write the solution file {solution_path}"))?; - - Ok(solution_path) - } -} - -#[cfg(test)] -mod tests { - use serde::Deserialize; - - use super::*; - - #[derive(Deserialize)] - struct ExerciseInfo { - dir: String, - } - - #[derive(Deserialize)] - struct InfoFile { - exercises: Vec, - } - - #[test] - fn dirs() { - let exercises = toml_edit::de::from_str::(EMBEDDED_FILES.info_file) - .expect("Failed to parse `info.toml`") - .exercises; - - assert_eq!(exercises.len(), EMBEDDED_FILES.exercise_files.len()); - - for (exercise, exercise_files) in exercises.iter().zip(EMBEDDED_FILES.exercise_files) { - assert_eq!( - exercise.dir, - EMBEDDED_FILES.exercise_dirs[exercise_files.dir_ind].name, - ); - } - } -} diff --git a/src/exercise.rs b/src/exercise.rs deleted file mode 100644 index fdfbc4f6ea..0000000000 --- a/src/exercise.rs +++ /dev/null @@ -1,211 +0,0 @@ -use anyhow::Result; -use crossterm::{ - QueueableCommand, - style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor}, -}; -use std::io::{self, StdoutLock, Write}; - -use crate::{ - cmd::CmdRunner, - term::{self, CountedWrite, terminal_file_link, write_ansi}, -}; - -/// The initial capacity of the output buffer. -pub const OUTPUT_CAPACITY: usize = 1 << 14; - -pub fn solution_link_line(stdout: &mut StdoutLock, solution_path: &str) -> io::Result<()> { - stdout.queue(SetAttribute(Attribute::Bold))?; - stdout.write_all(b"Solution")?; - stdout.queue(ResetColor)?; - stdout.write_all(b" for comparison: ")?; - if let Some(canonical_path) = term::canonicalize(solution_path) { - terminal_file_link(stdout, solution_path, &canonical_path, Color::Cyan)?; - } else { - stdout.write_all(solution_path.as_bytes())?; - } - stdout.write_all(b"\n") -} - -// Run an exercise binary and append its output to the `output` buffer. -// Compilation must be done before calling this method. -fn run_bin( - bin_name: &str, - mut output: Option<&mut Vec>, - cmd_runner: &CmdRunner, -) -> Result { - if let Some(output) = output.as_deref_mut() { - write_ansi(output, SetAttribute(Attribute::Underlined)); - output.extend_from_slice(b"Output"); - write_ansi(output, ResetColor); - output.push(b'\n'); - } - - let success = cmd_runner.run_debug_bin(bin_name, output.as_deref_mut())?; - - if let Some(output) = output { - if !success { - // This output is important to show the user that something went wrong. - // Otherwise, calling something like `exit(1)` in an exercise without further output - // leaves the user confused about why the exercise isn't done yet. - write_ansi(output, SetAttribute(Attribute::Bold)); - write_ansi(output, SetForegroundColor(Color::Red)); - output.extend_from_slice(b"The exercise didn't run successfully (nonzero exit code)"); - write_ansi(output, ResetColor); - output.push(b'\n'); - } - } - - Ok(success) -} - -/// See `info_file::ExerciseInfo` -pub struct Exercise { - pub dir: Option<&'static str>, - pub name: &'static str, - /// Path of the exercise file starting with the `exercises/` directory. - pub path: &'static str, - pub canonical_path: Option, - pub test: bool, - pub strict_clippy: bool, - pub hint: &'static str, - pub done: bool, -} - -impl Exercise { - pub fn terminal_file_link<'a>(&self, writer: &mut impl CountedWrite<'a>) -> io::Result<()> { - if let Some(canonical_path) = self.canonical_path.as_deref() { - return terminal_file_link(writer, self.path, canonical_path, Color::Blue); - } - - writer.write_str(self.path) - } -} - -pub trait RunnableExercise { - fn name(&self) -> &str; - fn dir(&self) -> Option<&str>; - fn strict_clippy(&self) -> bool; - fn test(&self) -> bool; - - // Compile, check and run the exercise or its solution (depending on `bin_name´). - // The output is written to the `output` buffer after clearing it. - fn run( - &self, - bin_name: &str, - mut output: Option<&mut Vec>, - cmd_runner: &CmdRunner, - ) -> Result { - if let Some(output) = output.as_deref_mut() { - output.clear(); - } - - let build_success = cmd_runner - .cargo("build", bin_name, output.as_deref_mut()) - .run("cargo build …")?; - if !build_success { - return Ok(false); - } - - // Discard the compiler output because it will be shown again by `cargo test` or Clippy. - if let Some(output) = output.as_deref_mut() { - output.clear(); - } - - if self.test() { - let output_is_some = output.is_some(); - let mut test_cmd = cmd_runner.cargo("test", bin_name, output.as_deref_mut()); - if output_is_some { - test_cmd.args(["--", "--color", "always", "--format", "pretty"]); - } - let test_success = test_cmd.run("cargo test …")?; - if !test_success { - run_bin(bin_name, output, cmd_runner)?; - return Ok(false); - } - - // Discard the compiler output because it will be shown again by Clippy. - if let Some(output) = output.as_deref_mut() { - output.clear(); - } - } - - let mut clippy_cmd = cmd_runner.cargo("clippy", bin_name, output.as_deref_mut()); - - // `--profile test` is required to also check code with `#[cfg(test)]`. - if FORCE_STRICT_CLIPPY || self.strict_clippy() { - clippy_cmd.args(["--profile", "test", "--", "-D", "warnings"]); - } else { - clippy_cmd.args(["--profile", "test"]); - } - - let clippy_success = clippy_cmd.run("cargo clippy …")?; - let run_success = run_bin(bin_name, output, cmd_runner)?; - - Ok(clippy_success && run_success) - } - - /// Compile, check and run the exercise. - /// The output is written to the `output` buffer after clearing it. - #[inline] - fn run_exercise(&self, output: Option<&mut Vec>, cmd_runner: &CmdRunner) -> Result { - self.run::(self.name(), output, cmd_runner) - } - - /// Compile, check and run the exercise's solution. - /// The output is written to the `output` buffer after clearing it. - fn run_solution(&self, output: Option<&mut Vec>, cmd_runner: &CmdRunner) -> Result { - let name = self.name(); - let mut bin_name = String::with_capacity(name.len() + 4); - bin_name.push_str(name); - bin_name.push_str("_sol"); - - self.run::(&bin_name, output, cmd_runner) - } - - fn sol_path(&self) -> String { - let name = self.name(); - - let mut path = if let Some(dir) = self.dir() { - // 14 = 10 + 1 + 3 - // solutions/ + / + .rs - let mut path = String::with_capacity(14 + dir.len() + name.len()); - path.push_str("solutions/"); - path.push_str(dir); - path.push('/'); - path - } else { - // 13 = 10 + 3 - // solutions/ + .rs - let mut path = String::with_capacity(13 + name.len()); - path.push_str("solutions/"); - path - }; - - path.push_str(name); - path.push_str(".rs"); - - path - } -} - -impl RunnableExercise for Exercise { - #[inline] - fn name(&self) -> &str { - self.name - } - - #[inline] - fn dir(&self) -> Option<&str> { - self.dir - } - - #[inline] - fn strict_clippy(&self) -> bool { - self.strict_clippy - } - - #[inline] - fn test(&self) -> bool { - self.test - } -} diff --git a/src/info_file.rs b/src/info_file.rs deleted file mode 100644 index 634bece9a8..0000000000 --- a/src/info_file.rs +++ /dev/null @@ -1,119 +0,0 @@ -use anyhow::{Context, Error, Result, bail}; -use serde::Deserialize; -use std::{fs, io::ErrorKind}; - -use crate::{embedded::EMBEDDED_FILES, exercise::RunnableExercise}; - -/// Deserialized from the `info.toml` file. -#[derive(Deserialize)] -pub struct ExerciseInfo { - /// Exercise's unique name. - pub name: String, - /// Exercise's directory name inside the `exercises/` directory. - pub dir: Option, - /// Run `cargo test` on the exercise. - #[serde(default = "default_true")] - pub test: bool, - /// Deny all Clippy warnings. - #[serde(default)] - pub strict_clippy: bool, - /// The exercise's hint to be shown to the user on request. - pub hint: String, - /// The exercise is already solved. Ignore it when checking that all exercises are unsolved. - #[serde(default)] - pub skip_check_unsolved: bool, -} -#[inline(always)] -const fn default_true() -> bool { - true -} - -impl ExerciseInfo { - /// Path to the exercise file starting with the `exercises/` directory. - pub fn path(&self) -> String { - let mut path = if let Some(dir) = &self.dir { - // 14 = 10 + 1 + 3 - // exercises/ + / + .rs - let mut path = String::with_capacity(14 + dir.len() + self.name.len()); - path.push_str("exercises/"); - path.push_str(dir); - path.push('/'); - path - } else { - // 13 = 10 + 3 - // exercises/ + .rs - let mut path = String::with_capacity(13 + self.name.len()); - path.push_str("exercises/"); - path - }; - - path.push_str(&self.name); - path.push_str(".rs"); - - path - } -} - -impl RunnableExercise for ExerciseInfo { - #[inline] - fn name(&self) -> &str { - &self.name - } - - #[inline] - fn dir(&self) -> Option<&str> { - self.dir.as_deref() - } - - #[inline] - fn strict_clippy(&self) -> bool { - self.strict_clippy - } - - #[inline] - fn test(&self) -> bool { - self.test - } -} - -/// The deserialized `info.toml` file. -#[derive(Deserialize)] -pub struct InfoFile { - /// For possible breaking changes in the future for community exercises. - pub format_version: u8, - /// Shown to users when starting with the exercises. - pub welcome_message: Option, - /// Shown to users after finishing all exercises. - pub final_message: Option, - /// List of all exercises. - pub exercises: Vec, -} - -impl InfoFile { - /// Official exercises: Parse the embedded `info.toml` file. - /// Community exercises: Parse the `info.toml` file in the current directory. - pub fn parse() -> Result { - // Read a local `info.toml` if it exists. - let slf = match fs::read_to_string("info.toml") { - Ok(file_content) => toml_edit::de::from_str::(&file_content) - .context("Failed to parse the `info.toml` file")?, - Err(e) => { - if e.kind() == ErrorKind::NotFound { - return toml_edit::de::from_str(EMBEDDED_FILES.info_file) - .context("Failed to parse the embedded `info.toml` file"); - } - - return Err(Error::from(e).context("Failed to read the `info.toml` file")); - } - }; - - if slf.exercises.is_empty() { - bail!("{NO_EXERCISES_ERR}"); - } - - Ok(slf) - } -} - -const NO_EXERCISES_ERR: &str = "There are no exercises yet! -Add at least one exercise before testing."; diff --git a/src/init.rs b/src/init.rs deleted file mode 100644 index 68011ed4e9..0000000000 --- a/src/init.rs +++ /dev/null @@ -1,224 +0,0 @@ -use anyhow::{Context, Result, bail}; -use crossterm::{ - QueueableCommand, - style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor}, -}; -use serde::Deserialize; -use std::{ - env::set_current_dir, - fs::{self, create_dir}, - io::{self, Write}, - path::{Path, PathBuf}, - process::{Command, Stdio}, -}; - -use crate::{ - cargo_toml::updated_cargo_toml, embedded::EMBEDDED_FILES, exercise::RunnableExercise, - info_file::InfoFile, term::press_enter_prompt, -}; - -#[derive(Deserialize)] -struct CargoLocateProject { - root: PathBuf, -} - -pub fn init() -> Result<()> { - let rustlings_dir = Path::new("rustlings"); - if rustlings_dir.exists() { - bail!(RUSTLINGS_DIR_ALREADY_EXISTS_ERR); - } - - let locate_project_output = Command::new("cargo") - .arg("locate-project") - .arg("-q") - .arg("--workspace") - .stdin(Stdio::null()) - .stderr(Stdio::null()) - .output() - .context( - "Failed to run the command `cargo locate-project …`\n\ - Did you already install Rust?\n\ - Try running `cargo --version` to diagnose the problem.", - )?; - - if !Command::new("cargo") - .arg("clippy") - .arg("--version") - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .context("Failed to run the command `cargo clippy --version`")? - .success() - { - bail!( - "Clippy, the official Rust linter, is missing.\n\ - Please install it first before initializing Rustlings." - ) - } - - let mut stdout = io::stdout().lock(); - let mut init_git = true; - - if locate_project_output.status.success() { - if Path::new("exercises").exists() && Path::new("solutions").exists() { - bail!(IN_INITIALIZED_DIR_ERR); - } - - let workspace_manifest = - serde_json::de::from_slice::(&locate_project_output.stdout) - .context( - "Failed to read the field `root` from the output of `cargo locate-project …`", - )? - .root; - - let workspace_manifest_content = fs::read_to_string(&workspace_manifest) - .with_context(|| format!("Failed to read the file {}", workspace_manifest.display()))?; - if !workspace_manifest_content.contains("[workspace]\n") - && !workspace_manifest_content.contains("workspace.") - { - bail!( - "The current directory is already part of a Cargo project.\n\ - Please initialize Rustlings in a different directory" - ); - } - - stdout.write_all(b"This command will create the directory `rustlings/` as a member of this Cargo workspace.\n\ - Press ENTER to continue ")?; - press_enter_prompt(&mut stdout)?; - - // Make sure "rustlings" is added to `workspace.members` by making - // Cargo initialize a new project. - let status = Command::new("cargo") - .arg("new") - .arg("-q") - .arg("--vcs") - .arg("none") - .arg("rustlings") - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .status()?; - if !status.success() { - bail!( - "Failed to initialize a new Cargo workspace member.\n\ - Please initialize Rustlings in a different directory" - ); - } - - stdout.write_all(b"The directory `rustlings` has been added to `workspace.members` in the `Cargo.toml` file of this Cargo workspace.\n")?; - fs::remove_dir_all("rustlings") - .context("Failed to remove the temporary directory `rustlings/`")?; - init_git = false; - } else { - stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\n\ - Press ENTER to continue ")?; - press_enter_prompt(&mut stdout)?; - } - - create_dir(rustlings_dir).context("Failed to create the `rustlings/` directory")?; - set_current_dir(rustlings_dir) - .context("Failed to change the current directory to `rustlings/`")?; - - let info_file = InfoFile::parse()?; - EMBEDDED_FILES - .init_exercises_dir(&info_file.exercises) - .context("Failed to initialize the `rustlings/exercises` directory")?; - - create_dir("solutions").context("Failed to create the `solutions/` directory")?; - fs::write( - "solutions/README.md", - include_bytes!("../solutions/README.md"), - ) - .context("Failed to create the file rustlings/solutions/README.md")?; - for dir in EMBEDDED_FILES.exercise_dirs { - let mut dir_path = String::with_capacity(10 + dir.name.len()); - dir_path.push_str("solutions/"); - dir_path.push_str(dir.name); - create_dir(&dir_path) - .with_context(|| format!("Failed to create the directory {dir_path}"))?; - } - for exercise_info in &info_file.exercises { - let solution_path = exercise_info.sol_path(); - fs::write(&solution_path, INIT_SOLUTION_FILE) - .with_context(|| format!("Failed to create the file {solution_path}"))?; - } - - let current_cargo_toml = include_str!("../dev-Cargo.toml"); - // Skip the first line (comment). - let newline_ind = current_cargo_toml - .as_bytes() - .iter() - .position(|c| *c == b'\n') - .context("The embedded `Cargo.toml` is empty or contains only one line")?; - let current_cargo_toml = current_cargo_toml - .get(newline_ind + 1..) - .context("The embedded `Cargo.toml` contains only one line")?; - let updated_cargo_toml = updated_cargo_toml(&info_file.exercises, current_cargo_toml, b"") - .context("Failed to generate `Cargo.toml`")?; - fs::write("Cargo.toml", updated_cargo_toml) - .context("Failed to create the file `rustlings/Cargo.toml`")?; - - fs::write("rust-analyzer.toml", RUST_ANALYZER_TOML) - .context("Failed to create the file `rustlings/rust-analyzer.toml`")?; - - fs::write(".gitignore", GITIGNORE) - .context("Failed to create the file `rustlings/.gitignore`")?; - - create_dir(".vscode").context("Failed to create the directory `rustlings/.vscode`")?; - fs::write(".vscode/extensions.json", VS_CODE_EXTENSIONS_JSON) - .context("Failed to create the file `rustlings/.vscode/extensions.json`")?; - - if init_git { - // Ignore any Git error because Git initialization is not required. - let _ = Command::new("git") - .arg("init") - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - } - - stdout.queue(SetForegroundColor(Color::Green))?; - stdout.write_all("Initialization done ✓".as_bytes())?; - stdout.queue(ResetColor)?; - stdout.write_all(b"\n\n")?; - - stdout.queue(SetAttribute(Attribute::Bold))?; - stdout.write_all(POST_INIT_MSG)?; - stdout.queue(ResetColor)?; - - Ok(()) -} - -const INIT_SOLUTION_FILE: &[u8] = b"fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. -} -"; - -pub const RUST_ANALYZER_TOML: &[u8] = br#"check.command = "clippy" -check.extraArgs = ["--profile", "test"] -cargo.targetDir = true -"#; - -const GITIGNORE: &[u8] = b"Cargo.lock -target/ -.vscode/ -"; - -pub const VS_CODE_EXTENSIONS_JSON: &[u8] = br#"{"recommendations":["rust-lang.rust-analyzer"]}"#; - -const IN_INITIALIZED_DIR_ERR: &str = "It looks like Rustlings is already initialized in this directory. - -If you already initialized Rustlings, run the command `rustlings` for instructions on getting started with the exercises. -Otherwise, please run `rustlings init` again in a different directory."; - -const RUSTLINGS_DIR_ALREADY_EXISTS_ERR: &str = - "A directory with the name `rustlings` already exists in the current directory. -You probably already initialized Rustlings. -Run `cd rustlings` -Then run `rustlings` again"; - -const POST_INIT_MSG: &[u8] = b"Run `cd rustlings` to go into the generated directory. -Then run `rustlings` to get started. -"; diff --git a/src/list.rs b/src/list.rs deleted file mode 100644 index a2eee9e16a..0000000000 --- a/src/list.rs +++ /dev/null @@ -1,134 +0,0 @@ -use anyhow::{Context, Result}; -use crossterm::{ - QueueableCommand, cursor, - event::{ - self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, MouseEventKind, - }, - terminal::{ - DisableLineWrap, EnableLineWrap, EnterAlternateScreen, LeaveAlternateScreen, - disable_raw_mode, enable_raw_mode, - }, -}; -use std::io::{self, StdoutLock, Write}; - -use crate::app_state::AppState; - -use self::state::{Filter, ListState}; - -mod scroll_state; -mod state; - -fn handle_list(app_state: &mut AppState, stdout: &mut StdoutLock) -> Result<()> { - let mut list_state = ListState::build(app_state, stdout)?; - let mut is_searching = false; - - loop { - match event::read().context("Failed to read terminal event")? { - Event::Key(key) => { - match key.kind { - KeyEventKind::Release => continue, - KeyEventKind::Press | KeyEventKind::Repeat => (), - } - - list_state.message.clear(); - - if is_searching { - match key.code { - KeyCode::Esc | KeyCode::Enter => { - is_searching = false; - list_state.search_query.clear(); - } - KeyCode::Char(c) => { - list_state.search_query.push(c); - list_state.apply_search_query(); - } - KeyCode::Backspace => { - list_state.search_query.pop(); - list_state.apply_search_query(); - } - _ => continue, - } - - list_state.draw(stdout)?; - continue; - } - - match key.code { - KeyCode::Char('q') => return Ok(()), - KeyCode::Down | KeyCode::Char('j') => list_state.select_next(), - KeyCode::Up | KeyCode::Char('k') => list_state.select_previous(), - KeyCode::Home | KeyCode::Char('g') => list_state.select_first(), - KeyCode::End | KeyCode::Char('G') => list_state.select_last(), - KeyCode::Char('d') => { - if list_state.filter() == Filter::Done { - list_state.set_filter(Filter::None); - list_state.message.push_str("Disabled filter DONE"); - } else { - list_state.set_filter(Filter::Done); - list_state.message.push_str( - "Enabled filter DONE │ Press d again to disable the filter", - ); - } - } - KeyCode::Char('p') => { - if list_state.filter() == Filter::Pending { - list_state.set_filter(Filter::None); - list_state.message.push_str("Disabled filter PENDING"); - } else { - list_state.set_filter(Filter::Pending); - list_state.message.push_str( - "Enabled filter PENDING │ Press p again to disable the filter", - ); - } - } - KeyCode::Char('r') => list_state.reset_selected()?, - KeyCode::Char('c') => { - if list_state.selected_to_current_exercise()? { - return Ok(()); - } - } - KeyCode::Char('s' | '/') => { - is_searching = true; - list_state.apply_search_query(); - } - // Redraw to remove the message. - KeyCode::Esc => (), - _ => continue, - } - } - Event::Mouse(event) => match event.kind { - MouseEventKind::ScrollDown => list_state.select_next(), - MouseEventKind::ScrollUp => list_state.select_previous(), - _ => continue, - }, - Event::Resize(width, height) => list_state.set_term_size(width, height), - // Ignore - Event::FocusGained | Event::FocusLost => continue, - } - - list_state.draw(stdout)?; - } -} - -pub fn list(app_state: &mut AppState) -> Result<()> { - let mut stdout = io::stdout().lock(); - stdout - .queue(EnterAlternateScreen)? - .queue(cursor::Hide)? - .queue(DisableLineWrap)? - .queue(EnableMouseCapture)?; - enable_raw_mode()?; - - let res = handle_list(app_state, &mut stdout); - - // Restore the terminal even if we got an error. - stdout - .queue(LeaveAlternateScreen)? - .queue(cursor::Show)? - .queue(EnableLineWrap)? - .queue(DisableMouseCapture)? - .flush()?; - disable_raw_mode()?; - - res -} diff --git a/src/list/scroll_state.rs b/src/list/scroll_state.rs deleted file mode 100644 index 2c02ed4f7e..0000000000 --- a/src/list/scroll_state.rs +++ /dev/null @@ -1,104 +0,0 @@ -pub struct ScrollState { - n_rows: usize, - max_n_rows_to_display: usize, - selected: Option, - offset: usize, - scroll_padding: usize, - max_scroll_padding: usize, -} - -impl ScrollState { - pub fn new(n_rows: usize, selected: Option, max_scroll_padding: usize) -> Self { - Self { - n_rows, - max_n_rows_to_display: 0, - selected, - offset: selected.map_or(0, |selected| selected.saturating_sub(max_scroll_padding)), - scroll_padding: 0, - max_scroll_padding, - } - } - - #[inline] - pub fn offset(&self) -> usize { - self.offset - } - - fn update_offset(&mut self) { - let Some(selected) = self.selected else { - return; - }; - - let min_offset = (selected + self.scroll_padding) - .saturating_sub(self.max_n_rows_to_display.saturating_sub(1)); - let max_offset = selected.saturating_sub(self.scroll_padding); - let global_max_offset = self.n_rows.saturating_sub(self.max_n_rows_to_display); - - self.offset = self - .offset - .max(min_offset) - .min(max_offset) - .min(global_max_offset); - } - - #[inline] - pub fn selected(&self) -> Option { - self.selected - } - - pub fn set_selected(&mut self, selected: usize) { - self.selected = Some(selected); - self.update_offset(); - } - - pub fn select_next(&mut self) { - if let Some(selected) = self.selected { - self.set_selected((selected + 1).min(self.n_rows - 1)); - } - } - - pub fn select_previous(&mut self) { - if let Some(selected) = self.selected { - self.set_selected(selected.saturating_sub(1)); - } - } - - pub fn select_first(&mut self) { - if self.n_rows > 0 { - self.set_selected(0); - } - } - - pub fn select_last(&mut self) { - if self.n_rows > 0 { - self.set_selected(self.n_rows - 1); - } - } - - pub fn set_n_rows(&mut self, n_rows: usize) { - self.n_rows = n_rows; - - if self.n_rows == 0 { - self.selected = None; - return; - } - - self.set_selected(self.selected.map_or(0, |selected| selected.min(n_rows - 1))); - } - - #[inline] - fn update_scroll_padding(&mut self) { - self.scroll_padding = (self.max_n_rows_to_display / 4).min(self.max_scroll_padding); - } - - #[inline] - pub fn max_n_rows_to_display(&self) -> usize { - self.max_n_rows_to_display - } - - pub fn set_max_n_rows_to_display(&mut self, max_n_rows_to_display: usize) { - self.max_n_rows_to_display = max_n_rows_to_display; - self.update_scroll_padding(); - self.update_offset(); - } -} diff --git a/src/list/state.rs b/src/list/state.rs deleted file mode 100644 index ae65ec2be9..0000000000 --- a/src/list/state.rs +++ /dev/null @@ -1,424 +0,0 @@ -use anyhow::{Context, Result}; -use crossterm::{ - QueueableCommand, - cursor::{MoveTo, MoveToNextLine}, - style::{ - Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor, - }, - terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate}, -}; -use std::{ - fmt::Write as _, - io::{self, StdoutLock, Write}, -}; - -use crate::{ - app_state::AppState, - exercise::Exercise, - term::{CountedWrite, MaxLenWriter, progress_bar}, -}; - -use super::scroll_state::ScrollState; - -const COL_SPACING: usize = 2; -const SELECTED_ROW_ATTRIBUTES: Attributes = Attributes::none() - .with(Attribute::Reverse) - .with(Attribute::Bold); - -fn next_ln(stdout: &mut StdoutLock) -> io::Result<()> { - stdout - .queue(Clear(ClearType::UntilNewLine))? - .queue(MoveToNextLine(1))?; - Ok(()) -} - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum Filter { - Done, - Pending, - None, -} - -pub struct ListState<'a> { - /// Footer message to be displayed if not empty. - pub message: String, - pub search_query: String, - app_state: &'a mut AppState, - scroll_state: ScrollState, - name_col_padding: Vec, - path_col_padding: Vec, - filter: Filter, - term_width: u16, - term_height: u16, - show_footer: bool, -} - -impl<'a> ListState<'a> { - pub fn build(app_state: &'a mut AppState, stdout: &mut StdoutLock) -> Result { - stdout.queue(Clear(ClearType::All))?; - - let name_col_title_len = 4; - let path_col_title_len = 4; - let (name_col_width, path_col_width) = app_state.exercises().iter().fold( - (name_col_title_len, path_col_title_len), - |(name_col_width, path_col_width), exercise| { - ( - name_col_width.max(exercise.name.len()), - path_col_width.max(exercise.path.len()), - ) - }, - ); - let name_col_padding = vec![b' '; name_col_width + COL_SPACING]; - let path_col_padding = vec![b' '; path_col_width]; - - let filter = Filter::None; - let n_rows_with_filter = app_state.exercises().len(); - let selected = app_state.current_exercise_ind(); - - let (width, height) = terminal::size().context("Failed to get the terminal size")?; - let scroll_state = ScrollState::new(n_rows_with_filter, Some(selected), 5); - - let mut slf = Self { - message: String::with_capacity(128), - search_query: String::new(), - app_state, - scroll_state, - name_col_padding, - path_col_padding, - filter, - // Set by `set_term_size` - term_width: 0, - term_height: 0, - show_footer: true, - }; - - slf.set_term_size(width, height); - slf.draw(stdout)?; - - Ok(slf) - } - - pub fn set_term_size(&mut self, width: u16, height: u16) { - self.term_width = width; - self.term_height = height; - - if height == 0 { - return; - } - - let header_height = 1; - // 1 progress bar, 2 footer message lines. - let footer_height = 3; - self.show_footer = height > header_height + footer_height; - - self.scroll_state.set_max_n_rows_to_display( - height.saturating_sub(header_height + u16::from(self.show_footer) * footer_height) - as usize, - ); - } - - fn draw_exercise_name(&self, writer: &mut MaxLenWriter, exercise: &Exercise) -> io::Result<()> { - if !self.search_query.is_empty() { - if let Some((pre_highlight, highlight, post_highlight)) = exercise - .name - .find(&self.search_query) - .and_then(|ind| exercise.name.split_at_checked(ind)) - .and_then(|(pre_highlight, rest)| { - rest.split_at_checked(self.search_query.len()) - .map(|x| (pre_highlight, x.0, x.1)) - }) - { - writer.write_str(pre_highlight)?; - writer.stdout.queue(SetForegroundColor(Color::Magenta))?; - writer.write_str(highlight)?; - writer.stdout.queue(SetForegroundColor(Color::Reset))?; - return writer.write_str(post_highlight); - } - } - - writer.write_str(exercise.name) - } - - fn draw_rows( - &self, - stdout: &mut StdoutLock, - filtered_exercises: impl Iterator, - ) -> io::Result { - let current_exercise_ind = self.app_state.current_exercise_ind(); - let row_offset = self.scroll_state.offset(); - let mut n_displayed_rows = 0; - - for (exercise_ind, exercise) in filtered_exercises - .skip(row_offset) - .take(self.scroll_state.max_n_rows_to_display()) - { - let mut writer = MaxLenWriter::new(stdout, self.term_width as usize); - - if self.scroll_state.selected() == Some(row_offset + n_displayed_rows) { - // The crab emoji has the width of two ascii chars. - writer.add_to_len(2); - writer.stdout.write_all("🦀".as_bytes())?; - writer - .stdout - .queue(SetAttributes(SELECTED_ROW_ATTRIBUTES))?; - } else { - writer.write_ascii(b" ")?; - } - - if exercise_ind == current_exercise_ind { - writer.stdout.queue(SetForegroundColor(Color::Red))?; - writer.write_ascii(b">>>>>>> ")?; - } else { - writer.write_ascii(b" ")?; - } - - if exercise.done { - writer.stdout.queue(SetForegroundColor(Color::Green))?; - writer.write_ascii(b"DONE ")?; - } else { - writer.stdout.queue(SetForegroundColor(Color::Yellow))?; - writer.write_ascii(b"PENDING")?; - } - writer.stdout.queue(SetForegroundColor(Color::Reset))?; - writer.write_ascii(b" ")?; - - self.draw_exercise_name(&mut writer, exercise)?; - - writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?; - - // The list links aren't shown correctly in VS Code on Windows. - // But VS Code shows its own links anyway. - if self.app_state.vs_code() { - writer.write_str(exercise.path)?; - } else { - exercise.terminal_file_link(&mut writer)?; - } - - writer.write_ascii(&self.path_col_padding[exercise.path.len()..])?; - - next_ln(stdout)?; - stdout.queue(ResetColor)?; - n_displayed_rows += 1; - } - - Ok(n_displayed_rows) - } - - pub fn draw(&mut self, stdout: &mut StdoutLock) -> io::Result<()> { - if self.term_height == 0 { - return Ok(()); - } - - stdout.queue(BeginSynchronizedUpdate)?.queue(MoveTo(0, 0))?; - - // Header - let mut writer = MaxLenWriter::new(stdout, self.term_width as usize); - writer.write_ascii(b" Current State Name")?; - writer.write_ascii(&self.name_col_padding[4..])?; - writer.write_ascii(b"Path")?; - next_ln(stdout)?; - - // Rows - let iter = self.app_state.exercises().iter().enumerate(); - let n_displayed_rows = match self.filter { - Filter::Done => self.draw_rows(stdout, iter.filter(|(_, exercise)| exercise.done))?, - Filter::Pending => { - self.draw_rows(stdout, iter.filter(|(_, exercise)| !exercise.done))? - } - Filter::None => self.draw_rows(stdout, iter)?, - }; - - for _ in 0..self.scroll_state.max_n_rows_to_display() - n_displayed_rows { - next_ln(stdout)?; - } - - if self.show_footer { - progress_bar( - &mut MaxLenWriter::new(stdout, self.term_width as usize), - self.app_state.n_done(), - self.app_state.exercises().len() as u16, - self.term_width, - )?; - next_ln(stdout)?; - - let mut writer = MaxLenWriter::new(stdout, self.term_width as usize); - if self.message.is_empty() { - // Help footer message - if self.scroll_state.selected().is_some() { - writer.write_str("↓/j ↑/k home/g end/G | ontinue at | eset exercise")?; - next_ln(stdout)?; - writer = MaxLenWriter::new(stdout, self.term_width as usize); - - writer.write_ascii(b"earch | filter ")?; - } else { - // Nothing selected (and nothing shown), so only display filter and quit. - writer.write_ascii(b"filter ")?; - } - - match self.filter { - Filter::Done => { - writer - .stdout - .queue(SetForegroundColor(Color::Magenta))? - .queue(SetAttribute(Attribute::Underlined))?; - writer.write_ascii(b"one")?; - writer.stdout.queue(ResetColor)?; - writer.write_ascii(b"/

ending")?; - } - Filter::Pending => { - writer.write_ascii(b"one/")?; - writer - .stdout - .queue(SetForegroundColor(Color::Magenta))? - .queue(SetAttribute(Attribute::Underlined))?; - writer.write_ascii(b"

ending")?; - writer.stdout.queue(ResetColor)?; - } - Filter::None => writer.write_ascii(b"one/

ending")?, - } - - writer.write_ascii(b" | uit list")?; - } else { - writer.stdout.queue(SetForegroundColor(Color::Magenta))?; - writer.write_str(&self.message)?; - stdout.queue(ResetColor)?; - next_ln(stdout)?; - } - - next_ln(stdout)?; - } - - stdout.queue(EndSynchronizedUpdate)?.flush() - } - - fn update_rows(&mut self) { - let n_rows = match self.filter { - Filter::Done => self - .app_state - .exercises() - .iter() - .filter(|exercise| exercise.done) - .count(), - Filter::Pending => self - .app_state - .exercises() - .iter() - .filter(|exercise| !exercise.done) - .count(), - Filter::None => self.app_state.exercises().len(), - }; - - self.scroll_state.set_n_rows(n_rows); - } - - #[inline] - pub fn filter(&self) -> Filter { - self.filter - } - - pub fn set_filter(&mut self, filter: Filter) { - self.filter = filter; - self.update_rows(); - } - - #[inline] - pub fn select_next(&mut self) { - self.scroll_state.select_next(); - } - - #[inline] - pub fn select_previous(&mut self) { - self.scroll_state.select_previous(); - } - - #[inline] - pub fn select_first(&mut self) { - self.scroll_state.select_first(); - } - - #[inline] - pub fn select_last(&mut self) { - self.scroll_state.select_last(); - } - - fn selected_to_exercise_ind(&self, selected: usize) -> Result { - match self.filter { - Filter::Done => self - .app_state - .exercises() - .iter() - .enumerate() - .filter(|(_, exercise)| exercise.done) - .nth(selected) - .context("Invalid selection index") - .map(|(ind, _)| ind), - Filter::Pending => self - .app_state - .exercises() - .iter() - .enumerate() - .filter(|(_, exercise)| !exercise.done) - .nth(selected) - .context("Invalid selection index") - .map(|(ind, _)| ind), - Filter::None => Ok(selected), - } - } - - pub fn reset_selected(&mut self) -> Result<()> { - let Some(selected) = self.scroll_state.selected() else { - self.message.push_str("Nothing selected to reset!"); - return Ok(()); - }; - - let exercise_ind = self.selected_to_exercise_ind(selected)?; - let exercise_name = self.app_state.reset_exercise_by_ind(exercise_ind)?; - self.update_rows(); - write!( - self.message, - "The exercise `{exercise_name}` has been reset", - )?; - - Ok(()) - } - - pub fn apply_search_query(&mut self) { - self.message.push_str("search:"); - self.message.push_str(&self.search_query); - self.message.push('|'); - - if self.search_query.is_empty() { - return; - } - - let is_search_result = |exercise: &Exercise| exercise.name.contains(&self.search_query); - let mut iter = self.app_state.exercises().iter(); - let ind = match self.filter { - Filter::None => iter.position(is_search_result), - Filter::Done => iter - .filter(|exercise| exercise.done) - .position(is_search_result), - Filter::Pending => iter - .filter(|exercise| !exercise.done) - .position(is_search_result), - }; - - match ind { - Some(exercise_ind) => self.scroll_state.set_selected(exercise_ind), - None => self.message.push_str(" (not found)"), - } - } - - // Return `true` if there was something to select. - pub fn selected_to_current_exercise(&mut self) -> Result { - let Some(selected) = self.scroll_state.selected() else { - self.message.push_str("Nothing selected to continue at!"); - return Ok(false); - }; - - let exercise_ind = self.selected_to_exercise_ind(selected)?; - self.app_state.set_current_exercise_ind(exercise_ind)?; - - Ok(true) - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 29de56b3d2..0000000000 --- a/src/main.rs +++ /dev/null @@ -1,217 +0,0 @@ -use anyhow::{Context, Result, bail}; -use app_state::StateFileStatus; -use clap::{Parser, Subcommand}; -use std::{ - io::{self, IsTerminal, Write}, - path::Path, - process::ExitCode, -}; -use term::{clear_terminal, press_enter_prompt}; - -use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile}; - -mod app_state; -mod cargo_toml; -mod cmd; -mod dev; -mod embedded; -mod exercise; -mod info_file; -mod init; -mod list; -mod run; -mod term; -mod watch; - -const CURRENT_FORMAT_VERSION: u8 = 1; - -/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code -#[derive(Parser)] -#[command(version)] -struct Args { - #[command(subcommand)] - command: Option, - /// Manually run the current exercise using `r` in the watch mode. - /// Only use this if Rustlings fails to detect exercise file changes. - #[arg(long)] - manual_run: bool, -} - -#[derive(Subcommand)] -enum Subcommands { - /// Initialize the official Rustlings exercises - Init, - /// Run a single exercise. Runs the next pending exercise if the exercise name is not specified - Run { - /// The name of the exercise - name: Option, - }, - /// Check all the exercises, marking them as done or pending accordingly. - CheckAll, - /// Reset a single exercise - Reset { - /// The name of the exercise - name: String, - }, - /// Show a hint. Shows the hint of the next pending exercise if the exercise name is not specified - Hint { - /// The name of the exercise - name: Option, - }, - /// Commands for developing (community) Rustlings exercises - #[command(subcommand)] - Dev(DevCommands), -} - -fn main() -> Result { - let args = Args::parse(); - - if cfg!(not(debug_assertions)) && Path::new("dev/rustlings-repo.txt").exists() { - bail!("{OLD_METHOD_ERR}"); - } - - 'priority_cmd: { - match args.command { - Some(Subcommands::Init) => init::init().context("Initialization failed")?, - Some(Subcommands::Dev(dev_command)) => dev_command.run()?, - _ => break 'priority_cmd, - } - - return Ok(ExitCode::SUCCESS); - } - - if !Path::new("exercises").is_dir() { - println!("{PRE_INIT_MSG}"); - return Ok(ExitCode::FAILURE); - } - - let info_file = InfoFile::parse()?; - - if info_file.format_version > CURRENT_FORMAT_VERSION { - bail!(FORMAT_VERSION_HIGHER_ERR); - } - - let (mut app_state, state_file_status) = AppState::new( - info_file.exercises, - info_file.final_message.unwrap_or_default(), - )?; - - // Show the welcome message if the state file doesn't exist yet. - if let Some(welcome_message) = info_file.welcome_message { - match state_file_status { - StateFileStatus::NotRead => { - let mut stdout = io::stdout().lock(); - clear_terminal(&mut stdout)?; - - let welcome_message = welcome_message.trim_ascii(); - write!( - stdout, - "{welcome_message}\n\n\ - Press ENTER to continue " - )?; - press_enter_prompt(&mut stdout)?; - clear_terminal(&mut stdout)?; - // Flush to be able to show errors occurring before printing a newline to stdout. - stdout.flush()?; - } - StateFileStatus::Read => (), - } - } - - match args.command { - None => { - if !io::stdout().is_terminal() { - bail!("Unsupported or missing terminal/TTY"); - } - - let notify_exercise_names = if args.manual_run { - None - } else { - // For the notify event handler thread. - // Leaking is not a problem because the slice lives until the end of the program. - Some( - &*app_state - .exercises() - .iter() - .map(|exercise| exercise.name.as_bytes()) - .collect::>() - .leak(), - ) - }; - - watch::watch(&mut app_state, notify_exercise_names)?; - } - Some(Subcommands::Run { name }) => { - if let Some(name) = name { - app_state.set_current_exercise_by_name(&name)?; - } - return run::run(&mut app_state); - } - Some(Subcommands::CheckAll) => { - let mut stdout = io::stdout().lock(); - if let Some(first_pending_exercise_ind) = app_state.check_all_exercises(&mut stdout)? { - if app_state.current_exercise().done { - app_state.set_current_exercise_ind(first_pending_exercise_ind)?; - } - - stdout.write_all(b"\n\n")?; - let pending = app_state.n_pending(); - if pending == 1 { - stdout.write_all(b"One exercise pending: ")?; - } else { - write!( - stdout, - "{pending}/{} exercises pending. The first: ", - app_state.exercises().len(), - )?; - } - app_state - .current_exercise() - .terminal_file_link(&mut stdout)?; - stdout.write_all(b"\n")?; - - return Ok(ExitCode::FAILURE); - } else { - app_state.render_final_message(&mut stdout)?; - } - } - Some(Subcommands::Reset { name }) => { - app_state.set_current_exercise_by_name(&name)?; - let exercise_path = app_state.reset_current_exercise()?; - println!("The exercise {exercise_path} has been reset"); - } - Some(Subcommands::Hint { name }) => { - if let Some(name) = name { - app_state.set_current_exercise_by_name(&name)?; - } - println!("{}", app_state.current_exercise().hint); - } - // Handled in an earlier match. - Some(Subcommands::Init | Subcommands::Dev(_)) => (), - } - - Ok(ExitCode::SUCCESS) -} - -const OLD_METHOD_ERR: &str = - "You are trying to run Rustlings using the old method before version 6. -The new method doesn't include cloning the Rustlings' repository. -Please follow the instructions in `README.md`: -https://github.com/rust-lang/rustlings#getting-started"; - -const FORMAT_VERSION_HIGHER_ERR: &str = - "The format version specified in the `info.toml` file is higher than the last one supported. -It is possible that you have an outdated version of Rustlings. -Try to install the latest Rustlings version first."; - -const PRE_INIT_MSG: &str = r" - Welcome to... - _ _ _ - _ __ _ _ ___| |_| (_)_ __ __ _ ___ - | '__| | | / __| __| | | '_ \ / _` / __| - | | | |_| \__ \ |_| | | | | | (_| \__ \ - |_| \__,_|___/\__|_|_|_| |_|\__, |___/ - |___/ - -The `exercises/` directory couldn't be found in the current directory. -If you are just starting with Rustlings, run the command `rustlings init` to initialize it."; diff --git a/src/run.rs b/src/run.rs deleted file mode 100644 index 6f4f099b47..0000000000 --- a/src/run.rs +++ /dev/null @@ -1,60 +0,0 @@ -use anyhow::Result; -use crossterm::{ - QueueableCommand, - style::{Color, ResetColor, SetForegroundColor}, -}; -use std::{ - io::{self, Write}, - process::ExitCode, -}; - -use crate::{ - app_state::{AppState, ExercisesProgress}, - exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line}, -}; - -pub fn run(app_state: &mut AppState) -> Result { - let exercise = app_state.current_exercise(); - let mut output = Vec::with_capacity(OUTPUT_CAPACITY); - let success = exercise.run_exercise(Some(&mut output), app_state.cmd_runner())?; - - let mut stdout = io::stdout().lock(); - stdout.write_all(&output)?; - - if !success { - app_state.set_pending(app_state.current_exercise_ind())?; - - stdout.write_all(b"Ran ")?; - app_state - .current_exercise() - .terminal_file_link(&mut stdout)?; - stdout.write_all(b" with errors\n")?; - - return Ok(ExitCode::FAILURE); - } - - stdout.queue(SetForegroundColor(Color::Green))?; - stdout.write_all("✓ Successfully ran ".as_bytes())?; - stdout.write_all(exercise.path.as_bytes())?; - stdout.queue(ResetColor)?; - stdout.write_all(b"\n")?; - - if let Some(solution_path) = app_state.current_solution_path()? { - stdout.write_all(b"\n")?; - solution_link_line(&mut stdout, &solution_path)?; - stdout.write_all(b"\n")?; - } - - match app_state.done_current_exercise::(&mut stdout)? { - ExercisesProgress::NewPending | ExercisesProgress::CurrentPending => { - stdout.write_all(b"Next exercise: ")?; - app_state - .current_exercise() - .terminal_file_link(&mut stdout)?; - stdout.write_all(b"\n")?; - } - ExercisesProgress::AllDone => (), - } - - Ok(ExitCode::SUCCESS) -} diff --git a/src/term.rs b/src/term.rs deleted file mode 100644 index b7dcd9f101..0000000000 --- a/src/term.rs +++ /dev/null @@ -1,310 +0,0 @@ -use crossterm::{ - Command, QueueableCommand, - cursor::MoveTo, - style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor}, - terminal::{Clear, ClearType}, -}; -use std::{ - fmt, fs, - io::{self, BufRead, StdoutLock, Write}, -}; - -use crate::app_state::CheckProgress; - -pub struct MaxLenWriter<'a, 'lock> { - pub stdout: &'a mut StdoutLock<'lock>, - len: usize, - max_len: usize, -} - -impl<'a, 'lock> MaxLenWriter<'a, 'lock> { - #[inline] - pub fn new(stdout: &'a mut StdoutLock<'lock>, max_len: usize) -> Self { - Self { - stdout, - len: 0, - max_len, - } - } - - // Additional is for emojis that take more space. - #[inline] - pub fn add_to_len(&mut self, additional: usize) { - self.len += additional; - } -} - -pub trait CountedWrite<'lock> { - fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()>; - fn write_str(&mut self, unicode: &str) -> io::Result<()>; - fn stdout(&mut self) -> &mut StdoutLock<'lock>; -} - -impl<'lock> CountedWrite<'lock> for MaxLenWriter<'_, 'lock> { - fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> { - let n = ascii.len().min(self.max_len.saturating_sub(self.len)); - if n > 0 { - self.stdout.write_all(&ascii[..n])?; - self.len += n; - } - Ok(()) - } - - fn write_str(&mut self, unicode: &str) -> io::Result<()> { - if let Some((ind, c)) = unicode - .char_indices() - .take(self.max_len.saturating_sub(self.len)) - .last() - { - self.stdout - .write_all(&unicode.as_bytes()[..ind + c.len_utf8()])?; - self.len += ind + 1; - } - - Ok(()) - } - - #[inline] - fn stdout(&mut self) -> &mut StdoutLock<'lock> { - self.stdout - } -} - -impl<'a> CountedWrite<'a> for StdoutLock<'a> { - #[inline] - fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> { - self.write_all(ascii) - } - - #[inline] - fn write_str(&mut self, unicode: &str) -> io::Result<()> { - self.write_all(unicode.as_bytes()) - } - - #[inline] - fn stdout(&mut self) -> &mut StdoutLock<'a> { - self - } -} - -pub struct CheckProgressVisualizer<'a, 'lock> { - stdout: &'a mut StdoutLock<'lock>, - n_cols: usize, -} - -impl<'a, 'lock> CheckProgressVisualizer<'a, 'lock> { - const CHECKING_COLOR: Color = Color::Blue; - const DONE_COLOR: Color = Color::Green; - const PENDING_COLOR: Color = Color::Red; - - pub fn build(stdout: &'a mut StdoutLock<'lock>, term_width: u16) -> io::Result { - clear_terminal(stdout)?; - stdout.write_all("Checking all exercises…\n".as_bytes())?; - - // Legend - stdout.write_all(b"Color of exercise number: ")?; - stdout.queue(SetForegroundColor(Self::CHECKING_COLOR))?; - stdout.write_all(b"Checking")?; - stdout.queue(ResetColor)?; - stdout.write_all(b" - ")?; - stdout.queue(SetForegroundColor(Self::DONE_COLOR))?; - stdout.write_all(b"Done")?; - stdout.queue(ResetColor)?; - stdout.write_all(b" - ")?; - stdout.queue(SetForegroundColor(Self::PENDING_COLOR))?; - stdout.write_all(b"Pending")?; - stdout.queue(ResetColor)?; - stdout.write_all(b"\n")?; - - // Exercise numbers with up to 3 digits. - // +1 because the last column doesn't end with a whitespace. - let n_cols = usize::from(term_width + 1) / 4; - - Ok(Self { stdout, n_cols }) - } - - pub fn update(&mut self, progresses: &[CheckProgress]) -> io::Result<()> { - self.stdout.queue(MoveTo(0, 2))?; - - let mut exercise_num = 1; - for exercise_progress in progresses { - match exercise_progress { - CheckProgress::None => (), - CheckProgress::Checking => { - self.stdout - .queue(SetForegroundColor(Self::CHECKING_COLOR))?; - } - CheckProgress::Done => { - self.stdout.queue(SetForegroundColor(Self::DONE_COLOR))?; - } - CheckProgress::Pending => { - self.stdout.queue(SetForegroundColor(Self::PENDING_COLOR))?; - } - } - - write!(self.stdout, "{exercise_num:<3}")?; - self.stdout.queue(ResetColor)?; - - if exercise_num != progresses.len() { - if exercise_num % self.n_cols == 0 { - self.stdout.write_all(b"\n")?; - } else { - self.stdout.write_all(b" ")?; - } - - exercise_num += 1; - } - } - - self.stdout.flush() - } -} - -pub struct ProgressCounter<'a, 'lock> { - stdout: &'a mut StdoutLock<'lock>, - total: usize, - counter: usize, -} - -impl<'a, 'lock> ProgressCounter<'a, 'lock> { - pub fn new(stdout: &'a mut StdoutLock<'lock>, total: usize) -> io::Result { - write!(stdout, "Progress: 0/{total}")?; - stdout.flush()?; - - Ok(Self { - stdout, - total, - counter: 0, - }) - } - - pub fn increment(&mut self) -> io::Result<()> { - self.counter += 1; - write!(self.stdout, "\rProgress: {}/{}", self.counter, self.total)?; - self.stdout.flush() - } -} - -impl Drop for ProgressCounter<'_, '_> { - fn drop(&mut self) { - let _ = self.stdout.write_all(b"\n\n"); - } -} - -pub fn progress_bar<'a>( - writer: &mut impl CountedWrite<'a>, - progress: u16, - total: u16, - term_width: u16, -) -> io::Result<()> { - debug_assert!(total <= 999); - debug_assert!(progress <= total); - - const PREFIX: &[u8] = b"Progress: ["; - const PREFIX_WIDTH: u16 = PREFIX.len() as u16; - const POSTFIX_WIDTH: u16 = "] xxx/xxx".len() as u16; - const WRAPPER_WIDTH: u16 = PREFIX_WIDTH + POSTFIX_WIDTH; - const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4; - - if term_width < MIN_LINE_WIDTH { - writer.write_ascii(b"Progress: ")?; - // Integers are in ASCII. - return writer.write_ascii(format!("{progress}/{total}").as_bytes()); - } - - let stdout = writer.stdout(); - stdout.write_all(PREFIX)?; - - let width = term_width - WRAPPER_WIDTH; - let filled = (width * progress) / total; - - stdout.queue(SetForegroundColor(Color::Green))?; - for _ in 0..filled { - stdout.write_all(b"#")?; - } - - if filled < width { - stdout.write_all(b">")?; - } - - let width_minus_filled = width - filled; - if width_minus_filled > 1 { - let red_part_width = width_minus_filled - 1; - stdout.queue(SetForegroundColor(Color::Red))?; - for _ in 0..red_part_width { - stdout.write_all(b"-")?; - } - } - - stdout.queue(SetForegroundColor(Color::Reset))?; - - write!(stdout, "] {progress:>3}/{total}") -} - -pub fn clear_terminal(stdout: &mut StdoutLock) -> io::Result<()> { - stdout - .queue(MoveTo(0, 0))? - .queue(Clear(ClearType::All))? - .queue(Clear(ClearType::Purge)) - .map(|_| ()) -} - -pub fn press_enter_prompt(stdout: &mut StdoutLock) -> io::Result<()> { - stdout.flush()?; - io::stdin().lock().read_until(b'\n', &mut Vec::new())?; - stdout.write_all(b"\n") -} - -/// Canonicalize, convert to string and remove verbatim part on Windows. -pub fn canonicalize(path: &str) -> Option { - fs::canonicalize(path) - .ok()? - .into_os_string() - .into_string() - .ok() - .map(|mut path| { - // Windows itself can't handle its verbatim paths. - if cfg!(windows) && path.as_bytes().starts_with(br"\\?\") { - path.drain(..4); - } - - path - }) -} - -pub fn terminal_file_link<'a>( - writer: &mut impl CountedWrite<'a>, - path: &str, - canonical_path: &str, - color: Color, -) -> io::Result<()> { - writer - .stdout() - .queue(SetForegroundColor(color))? - .queue(SetAttribute(Attribute::Underlined))?; - writer.stdout().write_all(b"\x1b]8;;file://")?; - writer.stdout().write_all(canonical_path.as_bytes())?; - writer.stdout().write_all(b"\x1b\\")?; - // Only this part is visible. - writer.write_str(path)?; - writer.stdout().write_all(b"\x1b]8;;\x1b\\")?; - writer - .stdout() - .queue(SetForegroundColor(Color::Reset))? - .queue(SetAttribute(Attribute::NoUnderline))?; - - Ok(()) -} - -pub fn write_ansi(output: &mut Vec, command: impl Command) { - struct FmtWriter<'a>(&'a mut Vec); - - impl fmt::Write for FmtWriter<'_> { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.0.extend_from_slice(s.as_bytes()); - Ok(()) - } - } - - let _ = command.write_ansi(&mut FmtWriter(output)); -} diff --git a/src/watch.rs b/src/watch.rs deleted file mode 100644 index 3a56b4b65b..0000000000 --- a/src/watch.rs +++ /dev/null @@ -1,190 +0,0 @@ -use anyhow::{Error, Result}; -use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; -use std::{ - io::{self, Write}, - path::Path, - sync::{ - atomic::{AtomicBool, Ordering::Relaxed}, - mpsc::channel, - }, - time::Duration, -}; - -use crate::{ - app_state::{AppState, ExercisesProgress}, - list, -}; - -use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent}; - -mod notify_event; -mod state; -mod terminal_event; - -static EXERCISE_RUNNING: AtomicBool = AtomicBool::new(false); - -// Private unit type to force using the constructor function. -#[must_use = "When the guard is dropped, the input is unpaused"] -pub struct InputPauseGuard(()); - -impl InputPauseGuard { - #[inline] - pub fn scoped_pause() -> Self { - EXERCISE_RUNNING.store(true, Relaxed); - Self(()) - } -} - -impl Drop for InputPauseGuard { - #[inline] - fn drop(&mut self) { - EXERCISE_RUNNING.store(false, Relaxed); - } -} - -enum WatchEvent { - Input(InputEvent), - FileChange { exercise_ind: usize }, - TerminalResize { width: u16 }, - NotifyErr(notify::Error), - TerminalEventErr(io::Error), -} - -/// Returned by the watch mode to indicate what to do afterwards. -#[must_use] -enum WatchExit { - /// Exit the program. - Shutdown, - /// Enter the list mode and restart the watch mode afterwards. - List, -} - -fn run_watch( - app_state: &mut AppState, - notify_exercise_names: Option<&'static [&'static [u8]]>, -) -> Result { - let (watch_event_sender, watch_event_receiver) = channel(); - - let mut manual_run = false; - // Prevent dropping the guard until the end of the function. - // Otherwise, the file watcher exits. - let _watcher_guard = if let Some(exercise_names) = notify_exercise_names { - let notify_event_handler = - NotifyEventHandler::build(watch_event_sender.clone(), exercise_names)?; - - let mut watcher = RecommendedWatcher::new( - notify_event_handler, - Config::default() - .with_follow_symlinks(false) - .with_poll_interval(Duration::from_secs(1)), - ) - .inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?; - - watcher - .watch(Path::new("exercises"), RecursiveMode::Recursive) - .inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?; - - Some(watcher) - } else { - manual_run = true; - None - }; - - let mut watch_state = WatchState::build(app_state, watch_event_sender, manual_run)?; - let mut stdout = io::stdout().lock(); - - watch_state.run_current_exercise(&mut stdout)?; - - while let Ok(event) = watch_event_receiver.recv() { - match event { - WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise(&mut stdout)? { - ExercisesProgress::AllDone => break, - ExercisesProgress::NewPending => watch_state.run_current_exercise(&mut stdout)?, - ExercisesProgress::CurrentPending => (), - }, - WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise(&mut stdout)?, - WatchEvent::Input(InputEvent::Hint) => watch_state.show_hint(&mut stdout)?, - WatchEvent::Input(InputEvent::List) => return Ok(WatchExit::List), - WatchEvent::Input(InputEvent::CheckAll) => match watch_state - .check_all_exercises(&mut stdout)? - { - ExercisesProgress::AllDone => break, - ExercisesProgress::NewPending => watch_state.run_current_exercise(&mut stdout)?, - ExercisesProgress::CurrentPending => watch_state.render(&mut stdout)?, - }, - WatchEvent::Input(InputEvent::Reset) => watch_state.reset_exercise(&mut stdout)?, - WatchEvent::Input(InputEvent::Quit) => { - stdout.write_all(QUIT_MSG)?; - break; - } - WatchEvent::FileChange { exercise_ind } => { - watch_state.handle_file_change(exercise_ind, &mut stdout)?; - } - WatchEvent::TerminalResize { width } => { - watch_state.update_term_width(width, &mut stdout)?; - } - WatchEvent::NotifyErr(e) => return Err(Error::from(e).context(NOTIFY_ERR)), - WatchEvent::TerminalEventErr(e) => { - return Err(Error::from(e).context("Terminal event listener failed")); - } - } - } - - Ok(WatchExit::Shutdown) -} - -fn watch_list_loop( - app_state: &mut AppState, - notify_exercise_names: Option<&'static [&'static [u8]]>, -) -> Result<()> { - loop { - match run_watch(app_state, notify_exercise_names)? { - WatchExit::Shutdown => break Ok(()), - // It is much easier to exit the watch mode, launch the list mode and then restart - // the watch mode instead of trying to pause the watch threads and correct the - // watch state. - WatchExit::List => list::list(app_state)?, - } - } -} - -/// `notify_exercise_names` as None activates the manual run mode. -pub fn watch( - app_state: &mut AppState, - notify_exercise_names: Option<&'static [&'static [u8]]>, -) -> Result<()> { - #[cfg(not(windows))] - { - let stdin_fd = rustix::stdio::stdin(); - let mut termios = rustix::termios::tcgetattr(stdin_fd)?; - let original_local_modes = termios.local_modes; - // Disable stdin line buffering and hide input. - termios.local_modes -= - rustix::termios::LocalModes::ICANON | rustix::termios::LocalModes::ECHO; - rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?; - - let res = watch_list_loop(app_state, notify_exercise_names); - - termios.local_modes = original_local_modes; - rustix::termios::tcsetattr(stdin_fd, rustix::termios::OptionalActions::Now, &termios)?; - - res - } - - #[cfg(windows)] - watch_list_loop(app_state, notify_exercise_names) -} - -const QUIT_MSG: &[u8] = b" - -We hope you're enjoying learning Rust! -If you want to continue working on the exercises at a later point, you can simply run `rustlings` again in this directory. -"; - -const NOTIFY_ERR: &str = " -The automatic detection of exercise file changes failed :( -Please try running `rustlings` again. - -If you keep getting this error, run `rustlings --manual-run` to deactivate the file watcher. -You need to manually trigger running the current exercise using `r` then. -"; diff --git a/src/watch/notify_event.rs b/src/watch/notify_event.rs deleted file mode 100644 index 9c05f10dad..0000000000 --- a/src/watch/notify_event.rs +++ /dev/null @@ -1,132 +0,0 @@ -use anyhow::{Context, Result}; -use notify::{ - Event, EventKind, - event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode}, -}; -use std::{ - sync::{ - atomic::Ordering::Relaxed, - mpsc::{RecvTimeoutError, Sender, SyncSender, sync_channel}, - }, - thread, - time::Duration, -}; - -use super::{EXERCISE_RUNNING, WatchEvent}; - -const DEBOUNCE_DURATION: Duration = Duration::from_millis(200); - -pub struct NotifyEventHandler { - error_sender: Sender, - // Sends the index of the updated exercise. - update_sender: SyncSender, - // Used to report which exercise was modified. - exercise_names: &'static [&'static [u8]], -} - -impl NotifyEventHandler { - pub fn build( - watch_event_sender: Sender, - exercise_names: &'static [&'static [u8]], - ) -> Result { - let (update_sender, update_receiver) = sync_channel(0); - let error_sender = watch_event_sender.clone(); - - // Debouncer - thread::Builder::new() - .spawn(move || { - let mut exercise_updated = vec![false; exercise_names.len()]; - - loop { - match update_receiver.recv_timeout(DEBOUNCE_DURATION) { - Ok(exercise_ind) => exercise_updated[exercise_ind] = true, - Err(RecvTimeoutError::Timeout) => { - for (exercise_ind, updated) in exercise_updated.iter_mut().enumerate() { - if *updated { - if watch_event_sender - .send(WatchEvent::FileChange { exercise_ind }) - .is_err() - { - break; - } - - *updated = false; - } - } - } - Err(RecvTimeoutError::Disconnected) => break, - } - } - }) - .context("Failed to spawn a thread to debounce file changes")?; - - Ok(Self { - error_sender, - update_sender, - exercise_names, - }) - } -} - -impl notify::EventHandler for NotifyEventHandler { - fn handle_event(&mut self, input_event: notify::Result) { - if EXERCISE_RUNNING.load(Relaxed) { - return; - } - - let input_event = match input_event { - Ok(v) => v, - Err(e) => { - // An error occurs when the receiver is dropped. - // After dropping the receiver, the watcher guard should also be dropped. - let _ = self.error_sender.send(WatchEvent::NotifyErr(e)); - return; - } - }; - - match input_event.kind { - EventKind::Any => (), - EventKind::Modify(modify_kind) => match modify_kind { - ModifyKind::Any | ModifyKind::Data(_) => (), - ModifyKind::Name(rename_mode) => match rename_mode { - RenameMode::Any | RenameMode::To => (), - RenameMode::From | RenameMode::Both | RenameMode::Other => return, - }, - ModifyKind::Metadata(metadata_kind) => match metadata_kind { - MetadataKind::Any | MetadataKind::WriteTime => (), - MetadataKind::AccessTime - | MetadataKind::Permissions - | MetadataKind::Ownership - | MetadataKind::Extended - | MetadataKind::Other => return, - }, - ModifyKind::Other => return, - }, - EventKind::Access(access_kind) => match access_kind { - AccessKind::Any => (), - AccessKind::Close(access_mode) => match access_mode { - AccessMode::Any | AccessMode::Write => (), - AccessMode::Execute | AccessMode::Read | AccessMode::Other => return, - }, - AccessKind::Read | AccessKind::Open(_) | AccessKind::Other => return, - }, - EventKind::Create(_) | EventKind::Remove(_) | EventKind::Other => return, - } - - let _ = input_event - .paths - .into_iter() - .filter_map(|path| { - let file_name = path.file_name()?.to_str()?.as_bytes(); - - let [file_name_without_ext @ .., b'.', b'r', b's'] = file_name else { - return None; - }; - - self.exercise_names - .iter() - .position(|exercise_name| *exercise_name == file_name_without_ext) - }) - .try_for_each(|exercise_ind| self.update_sender.send(exercise_ind)); - } -} diff --git a/src/watch/state.rs b/src/watch/state.rs deleted file mode 100644 index 2413becd28..0000000000 --- a/src/watch/state.rs +++ /dev/null @@ -1,299 +0,0 @@ -use anyhow::{Context, Result}; -use crossterm::{ - QueueableCommand, - style::{ - Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor, - }, - terminal, -}; -use std::{ - io::{self, Read, StdoutLock, Write}, - sync::mpsc::{Sender, SyncSender, sync_channel}, - thread, -}; - -use crate::{ - app_state::{AppState, ExercisesProgress}, - clear_terminal, - exercise::{OUTPUT_CAPACITY, RunnableExercise, solution_link_line}, - term::progress_bar, -}; - -use super::{InputPauseGuard, WatchEvent, terminal_event::terminal_event_handler}; - -const HEADING_ATTRIBUTES: Attributes = Attributes::none() - .with(Attribute::Bold) - .with(Attribute::Underlined); - -#[derive(PartialEq, Eq)] -enum DoneStatus { - DoneWithSolution(String), - DoneWithoutSolution, - Pending, -} - -pub struct WatchState<'a> { - app_state: &'a mut AppState, - output: Vec, - show_hint: bool, - done_status: DoneStatus, - manual_run: bool, - term_width: u16, - terminal_event_unpause_sender: SyncSender<()>, -} - -impl<'a> WatchState<'a> { - pub fn build( - app_state: &'a mut AppState, - watch_event_sender: Sender, - manual_run: bool, - ) -> Result { - let term_width = terminal::size() - .context("Failed to get the terminal size")? - .0; - - let (terminal_event_unpause_sender, terminal_event_unpause_receiver) = sync_channel(0); - - thread::Builder::new() - .spawn(move || { - terminal_event_handler( - watch_event_sender, - terminal_event_unpause_receiver, - manual_run, - ) - }) - .context("Failed to spawn a thread to handle terminal events")?; - - Ok(Self { - app_state, - output: Vec::with_capacity(OUTPUT_CAPACITY), - show_hint: false, - done_status: DoneStatus::Pending, - manual_run, - term_width, - terminal_event_unpause_sender, - }) - } - - pub fn run_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> { - // Ignore any input until running the exercise is done. - let _input_pause_guard = InputPauseGuard::scoped_pause(); - - self.show_hint = false; - - writeln!( - stdout, - "\nChecking the exercise `{}`. Please wait…", - self.app_state.current_exercise().name, - )?; - - let success = self - .app_state - .current_exercise() - .run_exercise(Some(&mut self.output), self.app_state.cmd_runner())?; - self.output.push(b'\n'); - if success { - self.done_status = - if let Some(solution_path) = self.app_state.current_solution_path()? { - DoneStatus::DoneWithSolution(solution_path) - } else { - DoneStatus::DoneWithoutSolution - }; - } else { - self.app_state - .set_pending(self.app_state.current_exercise_ind())?; - - self.done_status = DoneStatus::Pending; - } - - self.render(stdout)?; - Ok(()) - } - - pub fn reset_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> { - clear_terminal(stdout)?; - - stdout.write_all(b"Resetting will undo all your changes to the file ")?; - stdout.write_all(self.app_state.current_exercise().path.as_bytes())?; - stdout.write_all(b"\nReset (y/n)? ")?; - stdout.flush()?; - - { - let mut stdin = io::stdin().lock(); - let mut answer = [0]; - loop { - stdin - .read_exact(&mut answer) - .context("Failed to read the user's input")?; - - match answer[0] { - b'y' | b'Y' => { - self.app_state.reset_current_exercise()?; - - // The file watcher reruns the exercise otherwise. - if self.manual_run { - self.run_current_exercise(stdout)?; - } - } - b'n' | b'N' => self.render(stdout)?, - _ => continue, - } - - break; - } - } - - self.terminal_event_unpause_sender.send(())?; - - Ok(()) - } - - pub fn handle_file_change( - &mut self, - exercise_ind: usize, - stdout: &mut StdoutLock, - ) -> Result<()> { - if self.app_state.current_exercise_ind() != exercise_ind { - return Ok(()); - } - - self.run_current_exercise(stdout) - } - - /// Move on to the next exercise if the current one is done. - pub fn next_exercise(&mut self, stdout: &mut StdoutLock) -> Result { - match self.done_status { - DoneStatus::DoneWithSolution(_) | DoneStatus::DoneWithoutSolution => (), - DoneStatus::Pending => return Ok(ExercisesProgress::CurrentPending), - } - - self.app_state.done_current_exercise::(stdout) - } - - fn show_prompt(&self, stdout: &mut StdoutLock) -> io::Result<()> { - if self.done_status != DoneStatus::Pending { - stdout.queue(SetAttribute(Attribute::Bold))?; - stdout.write_all(b"n")?; - stdout.queue(ResetColor)?; - stdout.write_all(b":")?; - stdout.queue(SetAttribute(Attribute::Underlined))?; - stdout.write_all(b"next")?; - stdout.queue(ResetColor)?; - stdout.write_all(b" / ")?; - } - - let mut show_key = |key, postfix| { - stdout.queue(SetAttribute(Attribute::Bold))?; - stdout.write_all(&[key])?; - stdout.queue(ResetColor)?; - stdout.write_all(postfix) - }; - - if self.manual_run { - show_key(b'r', b":run / ")?; - } - - if !self.show_hint { - show_key(b'h', b":hint / ")?; - } - - show_key(b'l', b":list / ")?; - show_key(b'c', b":check all / ")?; - show_key(b'x', b":reset / ")?; - show_key(b'q', b":quit ? ")?; - - stdout.flush() - } - - pub fn render(&self, stdout: &mut StdoutLock) -> io::Result<()> { - // Prevent having the first line shifted if clearing wasn't successful. - stdout.write_all(b"\n")?; - clear_terminal(stdout)?; - - stdout.write_all(&self.output)?; - - if self.show_hint { - stdout - .queue(SetAttributes(HEADING_ATTRIBUTES))? - .queue(SetForegroundColor(Color::Cyan))?; - stdout.write_all(b"Hint")?; - stdout.queue(ResetColor)?; - stdout.write_all(b"\n")?; - - stdout.write_all(self.app_state.current_exercise().hint.as_bytes())?; - stdout.write_all(b"\n\n")?; - } - - if self.done_status != DoneStatus::Pending { - stdout - .queue(SetAttribute(Attribute::Bold))? - .queue(SetForegroundColor(Color::Green))?; - stdout.write_all("Exercise done ✓".as_bytes())?; - stdout.queue(ResetColor)?; - stdout.write_all(b"\n")?; - - if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status { - solution_link_line(stdout, solution_path)?; - } - - stdout.write_all( - "When done experimenting, enter `n` to move on to the next exercise 🦀\n\n" - .as_bytes(), - )?; - } - - progress_bar( - stdout, - self.app_state.n_done(), - self.app_state.exercises().len() as u16, - self.term_width, - )?; - - stdout.write_all(b"\nCurrent exercise: ")?; - self.app_state - .current_exercise() - .terminal_file_link(stdout)?; - stdout.write_all(b"\n\n")?; - - self.show_prompt(stdout)?; - - Ok(()) - } - - pub fn show_hint(&mut self, stdout: &mut StdoutLock) -> io::Result<()> { - if !self.show_hint { - self.show_hint = true; - self.render(stdout)?; - } - - Ok(()) - } - - pub fn check_all_exercises(&mut self, stdout: &mut StdoutLock) -> Result { - // Ignore any input until checking all exercises is done. - let _input_pause_guard = InputPauseGuard::scoped_pause(); - - if let Some(first_pending_exercise_ind) = self.app_state.check_all_exercises(stdout)? { - // Only change exercise if the current one is done. - if self.app_state.current_exercise().done { - self.app_state - .set_current_exercise_ind(first_pending_exercise_ind)?; - Ok(ExercisesProgress::NewPending) - } else { - Ok(ExercisesProgress::CurrentPending) - } - } else { - self.app_state.render_final_message(stdout)?; - Ok(ExercisesProgress::AllDone) - } - } - - pub fn update_term_width(&mut self, width: u16, stdout: &mut StdoutLock) -> io::Result<()> { - if self.term_width != width { - self.term_width = width; - self.render(stdout)?; - } - - Ok(()) - } -} diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs deleted file mode 100644 index 2400a3df54..0000000000 --- a/src/watch/terminal_event.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crossterm::event::{self, Event, KeyCode, KeyEventKind}; -use std::sync::{ - atomic::Ordering::Relaxed, - mpsc::{Receiver, Sender}, -}; - -use super::{EXERCISE_RUNNING, WatchEvent}; - -pub enum InputEvent { - Next, - Run, - Hint, - List, - CheckAll, - Reset, - Quit, -} - -pub fn terminal_event_handler( - sender: Sender, - unpause_receiver: Receiver<()>, - manual_run: bool, -) { - let last_watch_event = loop { - match event::read() { - Ok(Event::Key(key)) => { - match key.kind { - KeyEventKind::Release | KeyEventKind::Repeat => continue, - KeyEventKind::Press => (), - } - - if EXERCISE_RUNNING.load(Relaxed) { - continue; - } - - let input_event = match key.code { - KeyCode::Char('n') => InputEvent::Next, - KeyCode::Char('r') if manual_run => InputEvent::Run, - KeyCode::Char('h') => InputEvent::Hint, - KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List), - KeyCode::Char('c') => InputEvent::CheckAll, - KeyCode::Char('x') => { - if sender.send(WatchEvent::Input(InputEvent::Reset)).is_err() { - return; - } - - // Pause input until quitting the confirmation prompt. - if unpause_receiver.recv().is_err() { - return; - }; - - continue; - } - KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit), - _ => continue, - }; - - if sender.send(WatchEvent::Input(input_event)).is_err() { - return; - } - } - Ok(Event::Resize(width, _)) => { - if sender.send(WatchEvent::TerminalResize { width }).is_err() { - return; - } - } - Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue, - Err(e) => break WatchEvent::TerminalEventErr(e), - } - }; - - let _ = sender.send(last_watch_event); -} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs deleted file mode 100644 index bb3a084b22..0000000000 --- a/tests/integration_tests.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::{ - env::{self, consts::EXE_SUFFIX}, - process::{Command, Stdio}, - str::from_utf8, -}; - -enum Output<'a> { - FullStdout(&'a str), - PartialStdout(&'a str), - PartialStderr(&'a str), -} - -use Output::*; - -#[derive(Default)] -struct Cmd<'a> { - current_dir: Option<&'a str>, - args: &'a [&'a str], - output: Option>, -} - -impl<'a> Cmd<'a> { - #[inline] - fn current_dir(&mut self, current_dir: &'a str) -> &mut Self { - self.current_dir = Some(current_dir); - self - } - - #[inline] - fn args(&mut self, args: &'a [&'a str]) -> &mut Self { - self.args = args; - self - } - - #[inline] - fn output(&mut self, output: Output<'a>) -> &mut Self { - self.output = Some(output); - self - } - - fn assert(&self, success: bool) { - let rustlings_bin = { - let mut path = env::current_exe().unwrap(); - // Pop test binary name - path.pop(); - // Pop `/deps` - path.pop(); - - path.push("rustlings"); - let mut path = path.into_os_string(); - path.push(EXE_SUFFIX); - path - }; - - let mut cmd = Command::new(rustlings_bin); - - if let Some(current_dir) = self.current_dir { - cmd.current_dir(current_dir); - } - - cmd.args(self.args).stdin(Stdio::null()); - - let status = match self.output { - None => cmd - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .unwrap(), - Some(FullStdout(stdout)) => { - let output = cmd.stderr(Stdio::null()).output().unwrap(); - assert_eq!(from_utf8(&output.stdout).unwrap(), stdout); - output.status - } - Some(PartialStdout(stdout)) => { - let output = cmd.stderr(Stdio::null()).output().unwrap(); - assert!(from_utf8(&output.stdout).unwrap().contains(stdout)); - output.status - } - Some(PartialStderr(stderr)) => { - let output = cmd.stdout(Stdio::null()).output().unwrap(); - assert!(from_utf8(&output.stderr).unwrap().contains(stderr)); - output.status - } - }; - - assert_eq!(status.success(), success, "{cmd:?}"); - } - - #[inline] - fn success(&self) { - self.assert(true); - } - - #[inline] - fn fail(&self) { - self.assert(false); - } -} - -#[test] -fn run_compilation_success() { - Cmd::default() - .current_dir("tests/test_exercises") - .args(&["run", "compilation_success"]) - .success(); -} - -#[test] -fn run_compilation_failure() { - Cmd::default() - .current_dir("tests/test_exercises") - .args(&["run", "compilation_failure"]) - .fail(); -} - -#[test] -fn run_test_success() { - Cmd::default() - .current_dir("tests/test_exercises") - .args(&["run", "test_success"]) - .output(PartialStdout("\nOutput from `main` function\n")) - .success(); -} - -#[test] -fn run_test_failure() { - Cmd::default() - .current_dir("tests/test_exercises") - .args(&["run", "test_failure"]) - .fail(); -} - -#[test] -fn run_exercise_not_in_info() { - Cmd::default() - .current_dir("tests/test_exercises") - .args(&["run", "not_in_info"]) - .fail(); -} - -#[test] -fn reset_without_exercise_name() { - Cmd::default().args(&["reset"]).fail(); -} - -#[test] -fn hint() { - Cmd::default() - .current_dir("tests/test_exercises") - .args(&["hint", "test_failure"]) - .output(FullStdout("The answer to everything: 42\n")) - .success(); -} - -#[test] -fn init() { - let test_dir = tempfile::TempDir::new().unwrap(); - let test_dir = test_dir.path().to_str().unwrap(); - - Cmd::default().current_dir(test_dir).fail(); - - Cmd::default() - .current_dir(test_dir) - .args(&["init"]) - .success(); - - // Running `init` after a successful initialization. - Cmd::default() - .current_dir(test_dir) - .args(&["init"]) - .output(PartialStderr("`cd rustlings`")) - .fail(); - - let initialized_dir = format!("{test_dir}/rustlings"); - - // Running `init` in the initialized directory. - Cmd::default() - .current_dir(&initialized_dir) - .args(&["init"]) - .output(PartialStderr("already initialized")) - .fail(); -} diff --git a/tests/test_exercises/dev/Cargo.toml b/tests/test_exercises/dev/Cargo.toml deleted file mode 100644 index 74dcc20ade..0000000000 --- a/tests/test_exercises/dev/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -bin = [ - { name = "compilation_success", path = "../exercises/compilation_success.rs" }, - { name = "compilation_failure", path = "../exercises/compilation_failure.rs" }, - { name = "test_success", path = "../exercises/test_success.rs" }, - { name = "test_failure", path = "../exercises/test_failure.rs" }, -] - -[package] -name = "test_exercises" -edition = "2024" -publish = false diff --git a/tests/test_exercises/exercises/compilation_failure.rs b/tests/test_exercises/exercises/compilation_failure.rs deleted file mode 100644 index 566856a88a..0000000000 --- a/tests/test_exercises/exercises/compilation_failure.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let -} \ No newline at end of file diff --git a/tests/test_exercises/exercises/compilation_success.rs b/tests/test_exercises/exercises/compilation_success.rs deleted file mode 100644 index f328e4d9d0..0000000000 --- a/tests/test_exercises/exercises/compilation_success.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/tests/test_exercises/exercises/not_in_info.rs b/tests/test_exercises/exercises/not_in_info.rs deleted file mode 100644 index f328e4d9d0..0000000000 --- a/tests/test_exercises/exercises/not_in_info.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/tests/test_exercises/exercises/test_failure.rs b/tests/test_exercises/exercises/test_failure.rs deleted file mode 100644 index 8c8d59d807..0000000000 --- a/tests/test_exercises/exercises/test_failure.rs +++ /dev/null @@ -1,9 +0,0 @@ -fn main() {} - -#[cfg(test)] -mod tests { - #[test] - fn fails() { - assert!(false); - } -} diff --git a/tests/test_exercises/exercises/test_success.rs b/tests/test_exercises/exercises/test_success.rs deleted file mode 100644 index 8c8a3c61d0..0000000000 --- a/tests/test_exercises/exercises/test_success.rs +++ /dev/null @@ -1,9 +0,0 @@ -fn main() { - println!("Output from `main` function"); -} - -#[cfg(test)] -mod tests { - #[test] - fn passes() {} -} diff --git a/tests/test_exercises/info.toml b/tests/test_exercises/info.toml deleted file mode 100644 index d91094c0ea..0000000000 --- a/tests/test_exercises/info.toml +++ /dev/null @@ -1,19 +0,0 @@ -format_version = 1 - -[[exercises]] -name = "compilation_success" -test = false -hint = "" - -[[exercises]] -name = "compilation_failure" -test = false -hint = "" - -[[exercises]] -name = "test_success" -hint = "" - -[[exercises]] -name = "test_failure" -hint = "The answer to everything: 42" diff --git a/website/.gitignore b/website/.gitignore deleted file mode 100644 index 648e07746e..0000000000 --- a/website/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/node_modules/ -/package-lock.json - -/public/ - -/static/main.css -/static/processed_images/ diff --git a/website/config.toml b/website/config.toml deleted file mode 100644 index 0c01dc7d26..0000000000 --- a/website/config.toml +++ /dev/null @@ -1,41 +0,0 @@ -base_url = "https://rustlings.rust-lang.org" -title = "Rustlings" -description = "Small exercises to get you used to reading and writing Rust code!" - -compile_sass = false -build_search_index = false - -[markdown] -highlight_code = true -highlight_theme = "dracula" - -insert_anchor_links = "heading" - -[extra] -logo_path = "images/happy_ferris.svg" - -[[extra.menu_items]] -name = "Rustlings" -url = "@/_index.md" -[[extra.menu_items]] -name = "Setup" -url = "@/setup/index.md" -[[extra.menu_items]] -name = "Usage" -url = "@/usage/index.md" -[[extra.menu_items]] -name = "Community Exercises" -url = "@/community-exercises/index.md" -[[extra.menu_items]] -name = "Q&A" -url = "https://github.com/rust-lang/rustlings/discussions/categories/q-a?discussions_q=" - -[[extra.footer_items]] -name = "Repository" -url = "https://github.com/rust-lang/rustlings" -[[extra.footer_items]] -name = "Changelog" -url = "https://github.com/rust-lang/rustlings/blob/main/CHANGELOG.md" -[[extra.footer_items]] -name = "MIT License" -url = "https://github.com/rust-lang/rustlings/blob/main/LICENSE" diff --git a/website/content/_index.md b/website/content/_index.md deleted file mode 100644 index 4bb4483b5c..0000000000 --- a/website/content/_index.md +++ /dev/null @@ -1,21 +0,0 @@ -+++ -+++ - -Small exercises to get you used to reading and writing [Rust](https://www.rust-lang.org) code - _Recommended in parallel to reading [the official Rust book](https://doc.rust-lang.org/book) 📚️_ - - - -## Quick start - -```bash -# Installation -cargo install rustlings -# Initialization -rustlings init -# Moving into new directory -cd rustlings -# Starting Rustlings -rustlings -``` - -Visit the [**setup**](@/setup/index.md) page for more details 🧰 diff --git a/website/content/community-exercises/index.md b/website/content/community-exercises/index.md deleted file mode 100644 index 0f713d7cc7..0000000000 --- a/website/content/community-exercises/index.md +++ /dev/null @@ -1,73 +0,0 @@ -+++ -title = "Community Exercises" -+++ - -## List of Community Exercises - -- 🇯🇵 [Japanese Rustlings](https://github.com/sotanengel/rustlings-jp):A Japanese translation of the Rustlings exercises. -- 🇨🇳 [Simplified Chinese Rustlings](https://github.com/SandmeyerX/rustlings-zh-cn): A simplified Chinese translation of the Rustlings exercises. - -> You can use the same `rustlings` program that you installed with `cargo install rustlings` to run community exercises. - -## Creating Community Exercises - -Rustling's support for community exercises allows you to create your own exercises to focus on some specific topic. -You could also offer a translation of the original Rustlings exercises as community exercises. - -### Getting Started - -To create community exercises, install Rustlings and run `rustlings dev new PROJECT_NAME`. -This command will, similar to `cargo new PROJECT_NAME`, create the template directory `PROJECT_NAME` with all what you need to get started. - -_Read the comments_ in the generated `info.toml` file to understand its format. -It allows you to set a custom welcome and final message and specify the metadata of every exercise. - -### Creating an Exercise - -Here is an example of the metadata of one exercise: - -```toml -[[exercises]] -name = "intro1" -hint = """ -To finish this exercise, you need to … -These links might help you …""" -``` - -After entering this in `info.toml`, create the file `intro1.rs` in the `exercises/` directory. -The exercise needs to contain a `main` function, but it can be empty. -Adding tests is recommended. -Look at the official Rustlings exercises for inspiration. - -You can optionally add a solution file `intro1.rs` to the `solutions/` directory. - -Now, run `rustlings dev check`. -It will tell you about any issues with your exercises. -For example, it will tell you to run `rustlings dev update` to update the `Cargo.toml` file to include the new exercise `intro1`. - -`rustlings dev check` will also run your solutions (if you have any) to make sure that they run successfully. - -That's it! -You finished your first exercise 🎉 - -### Cargo.toml - -Except of the `bin` list, you can modify the `Cargo.toml` file as you want. - -> The `bin` list is automatically updated by running `rustlings dev update` - -- You can add dependencies in the `[dependencies]` table. -- You might want to [configure some lints](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section) for all exercises. You can do so in the `[lints.rust]` and `[lints.clippy]` tables. - -### Publishing - -Now, add more exercises and publish them as a Git repository. - -Users just have to clone that repository and run `rustlings` in it to start working on your exercises (just like the official ones). - -One difference to the official exercises is that the solution files will not be hidden until the user finishes an exercise. -But you can trust your users to not open the solution too early 😉 - -### Sharing - -After publishing your community exercises, open an issue or a pull request in the [official Rustlings repository](https://github.com/rust-lang/rustlings) to add your project to the [list of community exercises](#list-of-community-exercises) 😃 diff --git a/website/content/setup/index.md b/website/content/setup/index.md deleted file mode 100644 index 54551ada7c..0000000000 --- a/website/content/setup/index.md +++ /dev/null @@ -1,78 +0,0 @@ -+++ -title = "Setup" -+++ - - - -## Installing Rust - -Before installing Rustlings, you must have the **latest version of Rust** installed. -Visit [www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) for further instructions. -This will also install _Cargo_, Rust's package/project manager. - -> 🐧 If you are on **Linux**, make sure you have `gcc` installed (_for a linker_). -> -> Debian: `sudo apt install gcc`\ -> Fedora: `sudo dnf install gcc` - -> 🍎 If you are on **MacOS**, make sure you have _Xcode and its developer tools_ installed: `xcode-select --install` - -## Installing Rustlings - -The following command will download and compile Rustlings: - -```bash -cargo install rustlings -``` - -{% details(summary="If the installation fails…") %} - -- Make sure you have the latest Rust version by running `rustup update` -- Try adding the `--locked` flag: `cargo install rustlings --locked` -- Otherwise, please [report the issue](https://github.com/rust-lang/rustlings/issues/new) - -{% end %} - -## Initialization - -After installing Rustlings, run the following command to initialize the `rustlings/` directory: - -```bash -rustlings init -``` - -{% details(summary="If the command rustlings can't be found…") %} - -You are probably using Linux and installed Rust using your package manager. - -Cargo installs binaries to the directory `~/.cargo/bin`. -Sadly, package managers often don't add `~/.cargo/bin` to your `PATH` environment variable. - -- Either add `~/.cargo/bin` manually to `PATH` -- Or uninstall Rust from the package manager and [install it using the official way with `rustup`](https://www.rust-lang.org/tools/install) - -{% end %} - -Now, go into the newly initialized directory and launch Rustlings for further instructions on getting started with the exercises: - -```bash -cd rustlings/ -rustlings -``` - -## Working environment - -### Editor - -Our general recommendation is [VS Code](https://code.visualstudio.com/) with the [rust-analyzer plugin](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). -But any editor that supports [rust-analyzer](https://rust-analyzer.github.io/) should be enough for working on the exercises. - -### Terminal - -While working with Rustlings, please use a modern terminal for the best user experience. -The default terminal on Linux and Mac should be sufficient. -On Windows, we recommend the [Windows Terminal](https://aka.ms/terminal). - -## Usage - -After being done with the setup, visit the [**usage**](@/usage/index.md) page for some info about using Rustlings 🚀 diff --git a/website/content/usage/index.md b/website/content/usage/index.md deleted file mode 100644 index 88dabf4ae4..0000000000 --- a/website/content/usage/index.md +++ /dev/null @@ -1,55 +0,0 @@ -+++ -title = "Usage" -+++ - - - -## Doing exercises - -The exercises are sorted by topic and can be found in the subdirectory `exercises/`. -For every topic, there is an additional `README.md` file with some resources to get you started on the topic. -We highly recommend that you have a look at them before you start 📚️ - -Most exercises contain an error that keeps them from compiling, and it's up to you to fix it! -Some exercises contain tests that need to pass for the exercise to be done ✅ - -Search for `TODO` and `todo!()` to find out what you need to change. -Ask for hints by entering `h` in the _watch mode_ 💡 - -## Watch Mode - -After the [initialization](@/setup/index.md#initialization), Rustlings can be launched by simply running the command `rustlings`. - -This will start the _watch mode_ which walks you through the exercises in a predefined order (what we think is best for newcomers). -It will rerun the current exercise automatically every time you change the exercise's file in the `exercises/` directory. - -{% details(summary="If detecting file changes in the exercises/ directory fails…") %} - -You can add the **`--manual-run`** flag (`rustlings --manual-run`) to manually rerun the current exercise by entering `r` in the watch mode. - -Please [report the issue](https://github.com/rust-lang/rustlings/issues/new) with some information about your operating system and whether you run Rustlings in a container or a virtual machine (e.g. WSL). - -{% end %} - -## Exercise List - -In the [watch mode](#watch-mode) (after launching `rustlings`), you can enter `l` to open the interactive exercise list. - -The list allows you to… - -- See the status of all exercises (done or pending) -- `c`: Continue at another exercise (temporarily skip some exercises or go back to a previous one) -- `r`: Reset status and file of the selected exercise (you need to _reload/reopen_ its file in your editor afterwards) - -See the footer of the list for all possible keys. - -## Questions? - -If you need any help while doing the exercises and the builtin hints aren't helpful, feel free to ask in the [_Q&A_ discussions](https://github.com/rust-lang/rustlings/discussions/categories/q-a?discussions_q=) if your question isn't answered there 💡 - -## Continuing On - -Once you've completed Rustlings, put your new knowledge to good use! -Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to. - -> If you want to create your own Rustlings exercises, visit the [**community exercises**](@/community-exercises/index.md) page 🏗️ diff --git a/website/input.css b/website/input.css deleted file mode 100644 index af0675d8b7..0000000000 --- a/website/input.css +++ /dev/null @@ -1,54 +0,0 @@ -@import 'tailwindcss'; - -@layer base { - h1 { - @apply text-4xl mt-3 mb-3 font-bold; - } - h2 { - @apply text-3xl mt-4 mb-1.5 font-bold; - } - h3 { - @apply text-2xl mt-5 mb-1.5 font-bold; - } - h4 { - @apply text-xl mt-6 mb-1.5 font-bold; - } - p { - @apply mb-2; - } - a { - @apply text-[#FFC832] underline hover:decoration-orange-400 transition duration-300; - } - ul { - @apply mt-2 mb-3 ml-1 list-disc list-inside marker:text-sky-600; - } - ol { - @apply mt-2 mb-3 ml-1 list-decimal list-inside marker:text-sky-500; - } - li { - @apply my-0.5; - } - code { - @apply bg-white/10 px-1 pb-px pt-1 rounded-md; - } - pre code { - @apply bg-inherit p-0 text-inherit; - } - hr { - @apply my-5 rounded-full; - } - img { - @apply md:w-3/4 lg:w-3/5; - } - blockquote { - @apply px-3 pt-2 pb-0.5 mb-4 mt-2 border-s-4 border-white/80 bg-white/7 rounded-sm; - } - - pre { - @apply px-2 pt-2 pb-px overflow-x-auto text-sm sm:text-base rounded-sm mt-2 mb-4 after:content-[attr(data-lang)] after:text-[8px] after:opacity-40 selection:bg-white/15; - } - pre code mark { - @apply pb-0.5 pt-1 pr-px text-inherit rounded-xs; - } -} - diff --git a/website/justfile b/website/justfile deleted file mode 100644 index 7efc3ef9ca..0000000000 --- a/website/justfile +++ /dev/null @@ -1,5 +0,0 @@ -zola: - zola serve --open - -tailwind: - npx @tailwindcss/cli -w -i input.css -o static/main.css diff --git a/website/package.json b/website/package.json deleted file mode 100644 index 38dd27e995..0000000000 --- a/website/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "@tailwindcss/cli": "^4.1" - } -} diff --git a/website/static/images/happy_ferris.svg b/website/static/images/happy_ferris.svg deleted file mode 100644 index c7f240dd97..0000000000 --- a/website/static/images/happy_ferris.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/static/images/panic.svg b/website/static/images/panic.svg deleted file mode 100644 index be55f5e09b..0000000000 --- a/website/static/images/panic.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/static/images/rust_logo.svg b/website/static/images/rust_logo.svg deleted file mode 100644 index 3b42cfe034..0000000000 --- a/website/static/images/rust_logo.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - diff --git a/website/templates/404.html b/website/templates/404.html deleted file mode 100644 index eb9d4691a7..0000000000 --- a/website/templates/404.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -

-

DON'T PANIC!

-

404: Page not found!

- - - - Back to homepage -
-{% endblock %} diff --git a/website/templates/anchor-link.html b/website/templates/anchor-link.html deleted file mode 100644 index c8644d96f7..0000000000 --- a/website/templates/anchor-link.html +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/website/templates/base.html b/website/templates/base.html deleted file mode 100644 index 1a55aebf67..0000000000 --- a/website/templates/base.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - {%- set timestamp = now(timestamp=true) -%} - - {%- if page.title -%} - {% set_global title = page.title %} - {%- elif section.title -%} - {% set_global title = section.title %} - {%- else -%} - {% set_global title = config.title %} - {%- endif -%} - - {%- if page.description -%} - {% set_global description = page.description %} - {%- elif section.description -%} - {% set_global description = section.description %} - {%- else -%} - {% set_global description = config.description %} - {%- endif -%} - - {%- if page.permalink -%} - {% set_global permalink = page.permalink %} - {%- elif section.permalink -%} - {% set_global permalink = section.permalink %} - {%- endif %} - - {%- block title -%}{{- title -}}{%- endblock -%} - - - - - - - - - - - {% if permalink %}{% endif %} - - - -
- - - -
- -
- {% block content %}{% endblock %} -
- -
-
- -
Rustlings is an official Rust project
-
- - -
- - diff --git a/website/templates/index.html b/website/templates/index.html deleted file mode 100644 index 0d2b2e3986..0000000000 --- a/website/templates/index.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-

Rustlings

- - {{ section.content | safe }} -
-{% endblock %} diff --git a/website/templates/page.html b/website/templates/page.html deleted file mode 100644 index b2f6c019c6..0000000000 --- a/website/templates/page.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-

{{ page.title }}

- -
- -
- - {{ page.content | safe }} -
-{% endblock %} diff --git a/website/templates/shortcodes/details.html b/website/templates/shortcodes/details.html deleted file mode 100644 index 1c07778c00..0000000000 --- a/website/templates/shortcodes/details.html +++ /dev/null @@ -1,9 +0,0 @@ -
- - {{ summary | safe }} (click to expand) - - -
- {{ body | markdown | safe }} -
-
From de07320d0c04af967668bc6b2b268a4529a3b5f5 Mon Sep 17 00:00:00 2001 From: BraCR10 Date: Sat, 16 Aug 2025 22:37:15 -0600 Subject: [PATCH 3/6] level 18th --- .rustlings-state.txt | 23 +++- exercises/15_traits/traits1.rs | 3 + exercises/15_traits/traits2.rs | 6 + exercises/15_traits/traits3.rs | 4 +- exercises/15_traits/traits4.rs | 2 +- exercises/15_traits/traits5.rs | 2 +- exercises/16_lifetimes/lifetimes1.rs | 6 +- exercises/16_lifetimes/lifetimes2.rs | 3 +- exercises/16_lifetimes/lifetimes3.rs | 6 +- exercises/17_tests/tests1.rs | 8 +- exercises/17_tests/tests2.rs | 8 +- exercises/17_tests/tests3.rs | 8 +- exercises/18_iterators/iterators1.rs | 8 +- exercises/18_iterators/iterators2.rs | 12 +- exercises/18_iterators/iterators3.rs | 18 ++- exercises/18_iterators/iterators4.rs | 1 + exercises/18_iterators/iterators5.rs | 6 + exercises/quizzes/quiz3.rs | 6 +- solutions/14_generics/generics2.rs | 28 ++++- solutions/15_traits/traits1.rs | 32 ++++- solutions/15_traits/traits2.rs | 27 ++++- solutions/15_traits/traits3.rs | 36 +++++- solutions/15_traits/traits4.rs | 35 +++++- solutions/15_traits/traits5.rs | 39 ++++++- solutions/16_lifetimes/lifetimes1.rs | 28 ++++- solutions/16_lifetimes/lifetimes2.rs | 33 +++++- solutions/16_lifetimes/lifetimes3.rs | 18 ++- solutions/17_tests/tests1.rs | 24 +++- solutions/17_tests/tests2.rs | 22 +++- solutions/17_tests/tests3.rs | 45 ++++++- solutions/18_iterators/iterators1.rs | 26 ++++- solutions/18_iterators/iterators2.rs | 56 ++++++++- solutions/18_iterators/iterators3.rs | 86 +++++++++++++- solutions/18_iterators/iterators4.rs | 72 +++++++++++- solutions/18_iterators/iterators5.rs | 168 ++++++++++++++++++++++++++- solutions/quizzes/quiz3.rs | 65 ++++++++++- 36 files changed, 895 insertions(+), 75 deletions(-) diff --git a/.rustlings-state.txt b/.rustlings-state.txt index c3678cb2a0..56f1a680a4 100644 --- a/.rustlings-state.txt +++ b/.rustlings-state.txt @@ -1,6 +1,6 @@ DON'T EDIT THIS FILE! -generics1 +box1 intro1 intro2 @@ -57,4 +57,23 @@ errors2 errors3 errors4 errors5 -errors6 \ No newline at end of file +errors6 +generics1 +generics2 +traits1 +traits2 +traits3 +traits4 +traits5 +quiz3 +lifetimes1 +lifetimes2 +lifetimes3 +tests1 +tests2 +tests3 +iterators1 +iterators2 +iterators3 +iterators4 +iterators5 \ No newline at end of file diff --git a/exercises/15_traits/traits1.rs b/exercises/15_traits/traits1.rs index 85be17ea82..13757c5ac3 100644 --- a/exercises/15_traits/traits1.rs +++ b/exercises/15_traits/traits1.rs @@ -6,6 +6,9 @@ trait AppendBar { impl AppendBar for String { // TODO: Implement `AppendBar` for the type `String`. + fn append_bar(self) -> Self { + self+"Bar" + } } fn main() { diff --git a/exercises/15_traits/traits2.rs b/exercises/15_traits/traits2.rs index d724dc288e..d33ab41636 100644 --- a/exercises/15_traits/traits2.rs +++ b/exercises/15_traits/traits2.rs @@ -4,6 +4,12 @@ trait AppendBar { // TODO: Implement the trait `AppendBar` for a vector of strings. // `append_bar` should push the string "Bar" into the vector. +impl AppendBar for Vec{ + fn append_bar(mut self) -> Self { + self.push("Bar".to_string()); + self + } +} fn main() { // You can optionally experiment here. diff --git a/exercises/15_traits/traits3.rs b/exercises/15_traits/traits3.rs index c244650da6..3bc158542e 100644 --- a/exercises/15_traits/traits3.rs +++ b/exercises/15_traits/traits3.rs @@ -3,7 +3,9 @@ trait Licensed { // implementors like the two structs below can share that default behavior // without repeating the function. // The default license information should be the string "Default license". - fn licensing_info(&self) -> String; + fn licensing_info(&self) -> String{ + "Default license".to_string() + } } struct SomeSoftware { diff --git a/exercises/15_traits/traits4.rs b/exercises/15_traits/traits4.rs index 80092a6466..105837144c 100644 --- a/exercises/15_traits/traits4.rs +++ b/exercises/15_traits/traits4.rs @@ -11,7 +11,7 @@ impl Licensed for SomeSoftware {} impl Licensed for OtherSoftware {} // TODO: Fix the compiler error by only changing the signature of this function. -fn compare_license_types(software1: ???, software2: ???) -> bool { +fn compare_license_types(software1: T, software2: U) -> bool { software1.licensing_info() == software2.licensing_info() } diff --git a/exercises/15_traits/traits5.rs b/exercises/15_traits/traits5.rs index 5b356ac738..f2fbdb121d 100644 --- a/exercises/15_traits/traits5.rs +++ b/exercises/15_traits/traits5.rs @@ -19,7 +19,7 @@ impl SomeTrait for OtherStruct {} impl OtherTrait for OtherStruct {} // TODO: Fix the compiler error by only changing the signature of this function. -fn some_func(item: ???) -> bool { +fn some_func(item: T) -> bool { item.some_function() && item.other_function() } diff --git a/exercises/16_lifetimes/lifetimes1.rs b/exercises/16_lifetimes/lifetimes1.rs index 19e2d398b5..ffa248bbfe 100644 --- a/exercises/16_lifetimes/lifetimes1.rs +++ b/exercises/16_lifetimes/lifetimes1.rs @@ -4,11 +4,11 @@ // not own their own data. What if their owner goes out of scope? // TODO: Fix the compiler error by updating the function signature. -fn longest(x: &str, y: &str) -> &str { +fn longest(x: &str, y: &str) -> String { if x.len() > y.len() { - x + x.to_string() } else { - y + y.to_string() } } diff --git a/exercises/16_lifetimes/lifetimes2.rs b/exercises/16_lifetimes/lifetimes2.rs index de5a5dfc79..409159eda9 100644 --- a/exercises/16_lifetimes/lifetimes2.rs +++ b/exercises/16_lifetimes/lifetimes2.rs @@ -11,9 +11,10 @@ fn main() { // TODO: Fix the compiler error by moving one line. let string1 = String::from("long string is long"); + let string2 = String::from("xyz"); + let result; { - let string2 = String::from("xyz"); result = longest(&string1, &string2); } println!("The longest string is '{result}'"); diff --git a/exercises/16_lifetimes/lifetimes3.rs b/exercises/16_lifetimes/lifetimes3.rs index 1cc27592c6..ad7c33d9ff 100644 --- a/exercises/16_lifetimes/lifetimes3.rs +++ b/exercises/16_lifetimes/lifetimes3.rs @@ -1,9 +1,9 @@ // Lifetimes are also needed when structs hold references. // TODO: Fix the compiler errors about the struct. -struct Book { - author: &str, - title: &str, +struct Book<'a> { + author: &'a str, + title: &'a str, } fn main() { diff --git a/exercises/17_tests/tests1.rs b/exercises/17_tests/tests1.rs index 7529f9f049..db9d726a2c 100644 --- a/exercises/17_tests/tests1.rs +++ b/exercises/17_tests/tests1.rs @@ -13,11 +13,11 @@ fn main() { mod tests { // TODO: Import `is_even`. You can use a wildcard to import everything in // the outer module. - + use super::*; #[test] fn you_can_assert() { // TODO: Test the function `is_even` with some values. - assert!(); - assert!(); - } + assert!(is_even(2)); + assert!(!is_even(3)); + } } diff --git a/exercises/17_tests/tests2.rs b/exercises/17_tests/tests2.rs index 0c6573e8f0..19bdf192ad 100644 --- a/exercises/17_tests/tests2.rs +++ b/exercises/17_tests/tests2.rs @@ -15,9 +15,9 @@ mod tests { #[test] fn you_can_assert_eq() { // TODO: Test the function `power_of_2` with some values. - assert_eq!(); - assert_eq!(); - assert_eq!(); - assert_eq!(); + assert_eq!(power_of_2(2),4); + assert_eq!(power_of_2(3),8); + assert_eq!(power_of_2(4),16); + assert_eq!(power_of_2(5),32); } } diff --git a/exercises/17_tests/tests3.rs b/exercises/17_tests/tests3.rs index 822184ef75..ec117438ba 100644 --- a/exercises/17_tests/tests3.rs +++ b/exercises/17_tests/tests3.rs @@ -29,20 +29,22 @@ mod tests { // TODO: This test should check if the rectangle has the size that we // pass to its constructor. let rect = Rectangle::new(10, 20); - assert_eq!(todo!(), 10); // Check width - assert_eq!(todo!(), 20); // Check height + assert_eq!(rect.width, 10); // Check width + assert_eq!(rect.height, 20); // Check height } // TODO: This test should check if the program panics when we try to create // a rectangle with negative width. #[test] + #[should_panic] fn negative_width() { let _rect = Rectangle::new(-10, 10); } // TODO: This test should check if the program panics when we try to create // a rectangle with negative height. - #[test] + #[test] + #[should_panic] fn negative_height() { let _rect = Rectangle::new(10, -10); } diff --git a/exercises/18_iterators/iterators1.rs b/exercises/18_iterators/iterators1.rs index ca937ed00e..2218bc89a7 100644 --- a/exercises/18_iterators/iterators1.rs +++ b/exercises/18_iterators/iterators1.rs @@ -13,13 +13,13 @@ mod tests { let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"]; // TODO: Create an iterator over the array. - let mut fav_fruits_iterator = todo!(); + let mut fav_fruits_iterator =my_fav_fruits.iter(); assert_eq!(fav_fruits_iterator.next(), Some(&"banana")); - assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()` + assert_eq!(fav_fruits_iterator.next(), Some(&"custard apple")); // TODO: Replace `todo!()` assert_eq!(fav_fruits_iterator.next(), Some(&"avocado")); - assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()` + assert_eq!(fav_fruits_iterator.next(), Some(&"peach")); // TODO: Replace `todo!()` assert_eq!(fav_fruits_iterator.next(), Some(&"raspberry")); - assert_eq!(fav_fruits_iterator.next(), todo!()); // TODO: Replace `todo!()` + assert_eq!(fav_fruits_iterator.next(), None); // TODO: Replace `todo!()` } } diff --git a/exercises/18_iterators/iterators2.rs b/exercises/18_iterators/iterators2.rs index 5903e657e9..c9c4a36a4f 100644 --- a/exercises/18_iterators/iterators2.rs +++ b/exercises/18_iterators/iterators2.rs @@ -5,24 +5,26 @@ // "hello" -> "Hello" fn capitalize_first(input: &str) -> String { let mut chars = input.chars(); - match chars.next() { + let char =match chars.next() { None => String::new(), - Some(first) => todo!(), - } + Some(first) => first.to_uppercase().collect(), + }; + + char+chars.as_str() } // TODO: Apply the `capitalize_first` function to a slice of string slices. // Return a vector of strings. // ["hello", "world"] -> ["Hello", "World"] fn capitalize_words_vector(words: &[&str]) -> Vec { - // ??? + words.iter().map(|word| capitalize_first(word)).collect() } // TODO: Apply the `capitalize_first` function again to a slice of string // slices. Return a single string. // ["hello", " ", "world"] -> "Hello World" fn capitalize_words_string(words: &[&str]) -> String { - // ??? + words.iter().map(|word| capitalize_first(word)).collect() } fn main() { diff --git a/exercises/18_iterators/iterators3.rs b/exercises/18_iterators/iterators3.rs index 6b1eca1734..4285690980 100644 --- a/exercises/18_iterators/iterators3.rs +++ b/exercises/18_iterators/iterators3.rs @@ -1,3 +1,4 @@ + #[derive(Debug, PartialEq, Eq)] enum DivisionError { // Example: 42 / 0 @@ -11,21 +12,30 @@ enum DivisionError { // TODO: Calculate `a` divided by `b` if `a` is evenly divisible by `b`. // Otherwise, return a suitable error. fn divide(a: i64, b: i64) -> Result { - todo!(); + if b==0 { return Err(DivisionError::DivideByZero) } ; + if a == i64::MIN && b == -1 { + Err(DivisionError::IntegerOverflow) + } else if a % b != 0 { + Err(DivisionError::NotDivisible) + } else { + Ok(a / b) + } } // TODO: Add the correct return type and complete the function body. // Desired output: `Ok([1, 11, 1426, 3])` -fn result_with_list() { +fn result_with_list() -> Result,DivisionError>{ let numbers = [27, 297, 38502, 81]; - let division_results = numbers.into_iter().map(|n| divide(n, 27)); + numbers.into_iter().map(|n| divide(n, 27)).collect() + } // TODO: Add the correct return type and complete the function body. // Desired output: `[Ok(1), Ok(11), Ok(1426), Ok(3)]` -fn list_of_results() { +fn list_of_results()->Vec> { let numbers = [27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)); + division_results.collect() } fn main() { diff --git a/exercises/18_iterators/iterators4.rs b/exercises/18_iterators/iterators4.rs index c296f0e426..42bc60eca8 100644 --- a/exercises/18_iterators/iterators4.rs +++ b/exercises/18_iterators/iterators4.rs @@ -10,6 +10,7 @@ fn factorial(num: u64) -> u64 { // - additional variables // For an extra challenge, don't use: // - recursion + (1..=num).product() } fn main() { diff --git a/exercises/18_iterators/iterators5.rs b/exercises/18_iterators/iterators5.rs index 7e434cc5f9..b2f64cd6da 100644 --- a/exercises/18_iterators/iterators5.rs +++ b/exercises/18_iterators/iterators5.rs @@ -28,6 +28,7 @@ fn count_for(map: &HashMap, value: Progress) -> usize { fn count_iterator(map: &HashMap, value: Progress) -> usize { // `map` is a hash map with `String` keys and `Progress` values. // map = { "variables1": Complete, "from_str": None, … } + map.values().filter(|&v| *v == value).count() } fn count_collection_for(collection: &[HashMap], value: Progress) -> usize { @@ -48,6 +49,11 @@ fn count_collection_iterator(collection: &[HashMap], value: Pr // `collection` is a slice of hash maps. // collection = [{ "variables1": Complete, "from_str": None, … }, // { "variables2": Complete, … }, … ] + collection + .iter() + .flat_map(|map| map.values()) + .filter(|&v| *v == value) + .count() } fn main() { diff --git a/exercises/quizzes/quiz3.rs b/exercises/quizzes/quiz3.rs index c877c5f8e6..5bb859fb20 100644 --- a/exercises/quizzes/quiz3.rs +++ b/exercises/quizzes/quiz3.rs @@ -12,14 +12,14 @@ // block to support alphabetical report cards in addition to numerical ones. // TODO: Adjust the struct as described above. -struct ReportCard { - grade: f32, +struct ReportCard { + grade: T, student_name: String, student_age: u8, } // TODO: Adjust the impl block as described above. -impl ReportCard { +impl ReportCard { fn print(&self) -> String { format!( "{} ({}) - achieved a grade of {}", diff --git a/solutions/14_generics/generics2.rs b/solutions/14_generics/generics2.rs index dcf2377a2f..14f3f7afea 100644 --- a/solutions/14_generics/generics2.rs +++ b/solutions/14_generics/generics2.rs @@ -1,4 +1,28 @@ +struct Wrapper { + value: T, +} + +impl Wrapper { + fn new(value: T) -> Self { + Wrapper { value } + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn store_u32_in_wrapper() { + assert_eq!(Wrapper::new(42).value, 42); + } + + #[test] + fn store_str_in_wrapper() { + assert_eq!(Wrapper::new("Foo").value, "Foo"); + } } diff --git a/solutions/15_traits/traits1.rs b/solutions/15_traits/traits1.rs index dcf2377a2f..790873f4da 100644 --- a/solutions/15_traits/traits1.rs +++ b/solutions/15_traits/traits1.rs @@ -1,4 +1,32 @@ +// The trait `AppendBar` has only one function which appends "Bar" to any object +// implementing this trait. +trait AppendBar { + fn append_bar(self) -> Self; +} + +impl AppendBar for String { + fn append_bar(self) -> Self { + self + "Bar" + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let s = String::from("Foo"); + let s = s.append_bar(); + println!("s: {s}"); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_foo_bar() { + assert_eq!(String::from("Foo").append_bar(), "FooBar"); + } + + #[test] + fn is_bar_bar() { + assert_eq!(String::from("").append_bar().append_bar(), "BarBar"); + } } diff --git a/solutions/15_traits/traits2.rs b/solutions/15_traits/traits2.rs index dcf2377a2f..0db93e0f4f 100644 --- a/solutions/15_traits/traits2.rs +++ b/solutions/15_traits/traits2.rs @@ -1,4 +1,27 @@ +trait AppendBar { + fn append_bar(self) -> Self; +} + +impl AppendBar for Vec { + fn append_bar(mut self) -> Self { + // ^^^ this is important + self.push(String::from("Bar")); + self + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_vec_pop_eq_bar() { + let mut foo = vec![String::from("Foo")].append_bar(); + assert_eq!(foo.pop().unwrap(), "Bar"); + assert_eq!(foo.pop().unwrap(), "Foo"); + } } diff --git a/solutions/15_traits/traits3.rs b/solutions/15_traits/traits3.rs index dcf2377a2f..3d8ec85ead 100644 --- a/solutions/15_traits/traits3.rs +++ b/solutions/15_traits/traits3.rs @@ -1,4 +1,36 @@ +trait Licensed { + fn licensing_info(&self) -> String { + "Default license".to_string() + } +} + +struct SomeSoftware { + version_number: i32, +} + +struct OtherSoftware { + version_number: String, +} + +impl Licensed for SomeSoftware {} +impl Licensed for OtherSoftware {} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_licensing_info_the_same() { + let licensing_info = "Default license"; + let some_software = SomeSoftware { version_number: 1 }; + let other_software = OtherSoftware { + version_number: "v2.0.0".to_string(), + }; + assert_eq!(some_software.licensing_info(), licensing_info); + assert_eq!(other_software.licensing_info(), licensing_info); + } } diff --git a/solutions/15_traits/traits4.rs b/solutions/15_traits/traits4.rs index dcf2377a2f..3675b8dfe1 100644 --- a/solutions/15_traits/traits4.rs +++ b/solutions/15_traits/traits4.rs @@ -1,4 +1,35 @@ +trait Licensed { + fn licensing_info(&self) -> String { + "Default license".to_string() + } +} + +struct SomeSoftware; +struct OtherSoftware; + +impl Licensed for SomeSoftware {} +impl Licensed for OtherSoftware {} + +fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> bool { + // ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ + software1.licensing_info() == software2.licensing_info() +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn compare_license_information() { + assert!(compare_license_types(SomeSoftware, OtherSoftware)); + } + + #[test] + fn compare_license_information_backwards() { + assert!(compare_license_types(OtherSoftware, SomeSoftware)); + } } diff --git a/solutions/15_traits/traits5.rs b/solutions/15_traits/traits5.rs index dcf2377a2f..1fb426a474 100644 --- a/solutions/15_traits/traits5.rs +++ b/solutions/15_traits/traits5.rs @@ -1,4 +1,39 @@ +trait SomeTrait { + fn some_function(&self) -> bool { + true + } +} + +trait OtherTrait { + fn other_function(&self) -> bool { + true + } +} + +struct SomeStruct; +impl SomeTrait for SomeStruct {} +impl OtherTrait for SomeStruct {} + +struct OtherStruct; +impl SomeTrait for OtherStruct {} +impl OtherTrait for OtherStruct {} + +fn some_func(item: impl SomeTrait + OtherTrait) -> bool { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + item.some_function() && item.other_function() +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_some_func() { + assert!(some_func(SomeStruct)); + assert!(some_func(OtherStruct)); + } } diff --git a/solutions/16_lifetimes/lifetimes1.rs b/solutions/16_lifetimes/lifetimes1.rs index dcf2377a2f..ca7b688da5 100644 --- a/solutions/16_lifetimes/lifetimes1.rs +++ b/solutions/16_lifetimes/lifetimes1.rs @@ -1,4 +1,28 @@ +// The Rust compiler needs to know how to check whether supplied references are +// valid, so that it can let the programmer know if a reference is at risk of +// going out of scope before it is used. Remember, references are borrows and do +// not own their own data. What if their owner goes out of scope? + +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + // ^^^^ ^^ ^^ ^^ + if x.len() > y.len() { + x + } else { + y + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_longest() { + assert_eq!(longest("abcd", "123"), "abcd"); + assert_eq!(longest("abc", "1234"), "1234"); + } } diff --git a/solutions/16_lifetimes/lifetimes2.rs b/solutions/16_lifetimes/lifetimes2.rs index dcf2377a2f..b0f2ef1fa3 100644 --- a/solutions/16_lifetimes/lifetimes2.rs +++ b/solutions/16_lifetimes/lifetimes2.rs @@ -1,4 +1,33 @@ +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let string1 = String::from("long string is long"); + // Solution1: You can move `strings2` out of the inner block so that it is + // not dropped before the print statement. + let string2 = String::from("xyz"); + let result; + { + result = longest(&string1, &string2); + } + println!("The longest string is '{result}'"); + // `string2` dropped at the end of the function. + + // ========================================================================= + + let string1 = String::from("long string is long"); + let result; + { + let string2 = String::from("xyz"); + result = longest(&string1, &string2); + // Solution2: You can move the print statement into the inner block so + // that it is executed before `string2` is dropped. + println!("The longest string is '{result}'"); + // `string2` dropped here (end of the inner scope). + } } diff --git a/solutions/16_lifetimes/lifetimes3.rs b/solutions/16_lifetimes/lifetimes3.rs index dcf2377a2f..16a5a6840c 100644 --- a/solutions/16_lifetimes/lifetimes3.rs +++ b/solutions/16_lifetimes/lifetimes3.rs @@ -1,4 +1,18 @@ +// Lifetimes are also needed when structs hold references. + +struct Book<'a> { + // ^^^^ added a lifetime annotation + author: &'a str, + // ^^ + title: &'a str, + // ^^ +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let book = Book { + author: "George Orwell", + title: "1984", + }; + + println!("{} by {}", book.title, book.author); } diff --git a/solutions/17_tests/tests1.rs b/solutions/17_tests/tests1.rs index dcf2377a2f..c52b8b16d7 100644 --- a/solutions/17_tests/tests1.rs +++ b/solutions/17_tests/tests1.rs @@ -1,4 +1,24 @@ +// Tests are important to ensure that your code does what you think it should +// do. + +fn is_even(n: i64) -> bool { + n % 2 == 0 +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + // When writing unit tests, it is common to import everything from the outer + // module (`super`) using a wildcard. + use super::*; + + #[test] + fn you_can_assert() { + assert!(is_even(0)); + assert!(!is_even(-1)); + // ^ You can assert `false` using the negation operator `!`. + } } diff --git a/solutions/17_tests/tests2.rs b/solutions/17_tests/tests2.rs index dcf2377a2f..39a0005e8e 100644 --- a/solutions/17_tests/tests2.rs +++ b/solutions/17_tests/tests2.rs @@ -1,4 +1,22 @@ +// Calculates the power of 2 using a bit shift. +// `1 << n` is equivalent to "2 to the power of n". +fn power_of_2(n: u8) -> u64 { + 1 << n +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn you_can_assert_eq() { + assert_eq!(power_of_2(0), 1); + assert_eq!(power_of_2(1), 2); + assert_eq!(power_of_2(2), 4); + assert_eq!(power_of_2(3), 8); + } } diff --git a/solutions/17_tests/tests3.rs b/solutions/17_tests/tests3.rs index dcf2377a2f..487fdc66ea 100644 --- a/solutions/17_tests/tests3.rs +++ b/solutions/17_tests/tests3.rs @@ -1,4 +1,45 @@ +struct Rectangle { + width: i32, + height: i32, +} + +impl Rectangle { + // Don't change this function. + fn new(width: i32, height: i32) -> Self { + if width <= 0 || height <= 0 { + // Returning a `Result` would be better here. But we want to learn + // how to test functions that can panic. + panic!("Rectangle width and height must be positive"); + } + + Rectangle { width, height } + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn correct_width_and_height() { + let rect = Rectangle::new(10, 20); + assert_eq!(rect.width, 10); // Check width + assert_eq!(rect.height, 20); // Check height + } + + #[test] + #[should_panic] // Added this attribute to check that the test panics. + fn negative_width() { + let _rect = Rectangle::new(-10, 10); + } + + #[test] + #[should_panic] // Added this attribute to check that the test panics. + fn negative_height() { + let _rect = Rectangle::new(10, -10); + } } diff --git a/solutions/18_iterators/iterators1.rs b/solutions/18_iterators/iterators1.rs index dcf2377a2f..93a6008aa5 100644 --- a/solutions/18_iterators/iterators1.rs +++ b/solutions/18_iterators/iterators1.rs @@ -1,4 +1,26 @@ +// When performing operations on elements within a collection, iterators are +// essential. This module helps you get familiar with the structure of using an +// iterator and how to go through elements within an iterable collection. + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + #[test] + fn iterators() { + let my_fav_fruits = ["banana", "custard apple", "avocado", "peach", "raspberry"]; + + // Create an iterator over the array. + let mut fav_fruits_iterator = my_fav_fruits.iter(); + + assert_eq!(fav_fruits_iterator.next(), Some(&"banana")); + assert_eq!(fav_fruits_iterator.next(), Some(&"custard apple")); + assert_eq!(fav_fruits_iterator.next(), Some(&"avocado")); + assert_eq!(fav_fruits_iterator.next(), Some(&"peach")); + assert_eq!(fav_fruits_iterator.next(), Some(&"raspberry")); + assert_eq!(fav_fruits_iterator.next(), None); + // ^^^^ reached the end + } } diff --git a/solutions/18_iterators/iterators2.rs b/solutions/18_iterators/iterators2.rs index dcf2377a2f..db05f293d7 100644 --- a/solutions/18_iterators/iterators2.rs +++ b/solutions/18_iterators/iterators2.rs @@ -1,4 +1,56 @@ +// In this exercise, you'll learn some of the unique advantages that iterators +// can offer. + +// "hello" -> "Hello" +fn capitalize_first(input: &str) -> String { + let mut chars = input.chars(); + match chars.next() { + None => String::new(), + Some(first) => first.to_uppercase().to_string() + chars.as_str(), + } +} + +// Apply the `capitalize_first` function to a slice of string slices. +// Return a vector of strings. +// ["hello", "world"] -> ["Hello", "World"] +fn capitalize_words_vector(words: &[&str]) -> Vec { + words.iter().map(|word| capitalize_first(word)).collect() +} + +// Apply the `capitalize_first` function again to a slice of string +// slices. Return a single string. +// ["hello", " ", "world"] -> "Hello World" +fn capitalize_words_string(words: &[&str]) -> String { + words.iter().map(|word| capitalize_first(word)).collect() +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_success() { + assert_eq!(capitalize_first("hello"), "Hello"); + } + + #[test] + fn test_empty() { + assert_eq!(capitalize_first(""), ""); + } + + #[test] + fn test_iterate_string_vec() { + let words = vec!["hello", "world"]; + assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]); + } + + #[test] + fn test_iterate_into_string() { + let words = vec!["hello", " ", "world"]; + assert_eq!(capitalize_words_string(&words), "Hello World"); + } } diff --git a/solutions/18_iterators/iterators3.rs b/solutions/18_iterators/iterators3.rs index dcf2377a2f..11aa1ec8ce 100644 --- a/solutions/18_iterators/iterators3.rs +++ b/solutions/18_iterators/iterators3.rs @@ -1,4 +1,86 @@ +#[derive(Debug, PartialEq, Eq)] +enum DivisionError { + // Example: 42 / 0 + DivideByZero, + // Only case for `i64`: `i64::MIN / -1` because the result is `i64::MAX + 1` + IntegerOverflow, + // Example: 5 / 2 = 2.5 + NotDivisible, +} + +fn divide(a: i64, b: i64) -> Result { + if b == 0 { + return Err(DivisionError::DivideByZero); + } + + if a == i64::MIN && b == -1 { + return Err(DivisionError::IntegerOverflow); + } + + if a % b != 0 { + return Err(DivisionError::NotDivisible); + } + + Ok(a / b) +} + +fn result_with_list() -> Result, DivisionError> { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + let numbers = [27, 297, 38502, 81]; + let division_results = numbers.into_iter().map(|n| divide(n, 27)); + // Collects to the expected return type. Returns the first error in the + // division results (if one exists). + division_results.collect() +} + +fn list_of_results() -> Vec> { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + let numbers = [27, 297, 38502, 81]; + let division_results = numbers.into_iter().map(|n| divide(n, 27)); + // Collects to the expected return type. + division_results.collect() +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_success() { + assert_eq!(divide(81, 9), Ok(9)); + } + + #[test] + fn test_divide_by_0() { + assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero)); + } + + #[test] + fn test_integer_overflow() { + assert_eq!(divide(i64::MIN, -1), Err(DivisionError::IntegerOverflow)); + } + + #[test] + fn test_not_divisible() { + assert_eq!(divide(81, 6), Err(DivisionError::NotDivisible)); + } + + #[test] + fn test_divide_0_by_something() { + assert_eq!(divide(0, 81), Ok(0)); + } + + #[test] + fn test_result_with_list() { + assert_eq!(result_with_list().unwrap(), [1, 11, 1426, 3]); + } + + #[test] + fn test_list_of_results() { + assert_eq!(list_of_results(), [Ok(1), Ok(11), Ok(1426), Ok(3)]); + } } diff --git a/solutions/18_iterators/iterators4.rs b/solutions/18_iterators/iterators4.rs index dcf2377a2f..4168835afc 100644 --- a/solutions/18_iterators/iterators4.rs +++ b/solutions/18_iterators/iterators4.rs @@ -1,4 +1,72 @@ +// 3 possible solutions are presented. + +// With `for` loop and a mutable variable. +fn factorial_for(num: u64) -> u64 { + let mut result = 1; + + for x in 2..=num { + result *= x; + } + + result +} + +// Equivalent to `factorial_for` but shorter and without a `for` loop and +// mutable variables. +fn factorial_fold(num: u64) -> u64 { + // Case num==0: The iterator 2..=0 is empty + // -> The initial value of `fold` is returned which is 1. + // Case num==1: The iterator 2..=1 is also empty + // -> The initial value 1 is returned. + // Case num==2: The iterator 2..=2 contains one element + // -> The initial value 1 is multiplied by 2 and the result + // is returned. + // Case num==3: The iterator 2..=3 contains 2 elements + // -> 1 * 2 is calculated, then the result 2 is multiplied by + // the second element 3 so the result 6 is returned. + // And so on… + #[allow(clippy::unnecessary_fold)] + (2..=num).fold(1, |acc, x| acc * x) +} + +// Equivalent to `factorial_fold` but with a built-in method that is suggested +// by Clippy. +fn factorial_product(num: u64) -> u64 { + (2..=num).product() +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn factorial_of_0() { + assert_eq!(factorial_for(0), 1); + assert_eq!(factorial_fold(0), 1); + assert_eq!(factorial_product(0), 1); + } + + #[test] + fn factorial_of_1() { + assert_eq!(factorial_for(1), 1); + assert_eq!(factorial_fold(1), 1); + assert_eq!(factorial_product(1), 1); + } + #[test] + fn factorial_of_2() { + assert_eq!(factorial_for(2), 2); + assert_eq!(factorial_fold(2), 2); + assert_eq!(factorial_product(2), 2); + } + + #[test] + fn factorial_of_4() { + assert_eq!(factorial_for(4), 24); + assert_eq!(factorial_fold(4), 24); + assert_eq!(factorial_product(4), 24); + } } diff --git a/solutions/18_iterators/iterators5.rs b/solutions/18_iterators/iterators5.rs index dcf2377a2f..067a117b92 100644 --- a/solutions/18_iterators/iterators5.rs +++ b/solutions/18_iterators/iterators5.rs @@ -1,4 +1,168 @@ +// Let's define a simple model to track Rustlings' exercise progress. Progress +// will be modelled using a hash map. The name of the exercise is the key and +// the progress is the value. Two counting functions were created to count the +// number of exercises with a given progress. Recreate this counting +// functionality using iterators. Try to not use imperative loops (for/while). + +use std::collections::HashMap; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Progress { + None, + Some, + Complete, +} + +fn count_for(map: &HashMap, value: Progress) -> usize { + let mut count = 0; + for val in map.values() { + if *val == value { + count += 1; + } + } + count +} + +fn count_iterator(map: &HashMap, value: Progress) -> usize { + // `map` is a hash map with `String` keys and `Progress` values. + // map = { "variables1": Complete, "from_str": None, … } + map.values().filter(|val| **val == value).count() +} + +fn count_collection_for(collection: &[HashMap], value: Progress) -> usize { + let mut count = 0; + for map in collection { + count += count_for(map, value); + } + count +} + +fn count_collection_iterator(collection: &[HashMap], value: Progress) -> usize { + // `collection` is a slice of hash maps. + // collection = [{ "variables1": Complete, "from_str": None, … }, + // { "variables2": Complete, … }, … ] + collection + .iter() + .map(|map| count_iterator(map, value)) + .sum() +} + +// Equivalent to `count_collection_iterator` and `count_iterator`, iterating as +// if the collection was a single container instead of a container of containers +// (and more accurately, a single iterator instead of an iterator of iterators). +fn count_collection_iterator_flat( + collection: &[HashMap], + value: Progress, +) -> usize { + // `collection` is a slice of hash maps. + // collection = [{ "variables1": Complete, "from_str": None, … }, + // { "variables2": Complete, … }, … ] + collection + .iter() + .flat_map(HashMap::values) // or just `.flatten()` when wanting the default iterator (`HashMap::iter`) + .filter(|val| **val == value) + .count() +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + use Progress::*; + + fn get_map() -> HashMap { + let mut map = HashMap::new(); + map.insert(String::from("variables1"), Complete); + map.insert(String::from("functions1"), Complete); + map.insert(String::from("hashmap1"), Complete); + map.insert(String::from("arc1"), Some); + map.insert(String::from("as_ref_mut"), None); + map.insert(String::from("from_str"), None); + + map + } + + fn get_vec_map() -> Vec> { + let map = get_map(); + + let mut other = HashMap::new(); + other.insert(String::from("variables2"), Complete); + other.insert(String::from("functions2"), Complete); + other.insert(String::from("if1"), Complete); + other.insert(String::from("from_into"), None); + other.insert(String::from("try_from_into"), None); + + vec![map, other] + } + + #[test] + fn count_complete() { + let map = get_map(); + assert_eq!(count_iterator(&map, Complete), 3); + } + + #[test] + fn count_some() { + let map = get_map(); + assert_eq!(count_iterator(&map, Some), 1); + } + + #[test] + fn count_none() { + let map = get_map(); + assert_eq!(count_iterator(&map, None), 2); + } + + #[test] + fn count_complete_equals_for() { + let map = get_map(); + let progress_states = [Complete, Some, None]; + for progress_state in progress_states { + assert_eq!( + count_for(&map, progress_state), + count_iterator(&map, progress_state), + ); + } + } + + #[test] + fn count_collection_complete() { + let collection = get_vec_map(); + assert_eq!(count_collection_iterator(&collection, Complete), 6); + assert_eq!(count_collection_iterator_flat(&collection, Complete), 6); + } + + #[test] + fn count_collection_some() { + let collection = get_vec_map(); + assert_eq!(count_collection_iterator(&collection, Some), 1); + assert_eq!(count_collection_iterator_flat(&collection, Some), 1); + } + + #[test] + fn count_collection_none() { + let collection = get_vec_map(); + assert_eq!(count_collection_iterator(&collection, None), 4); + assert_eq!(count_collection_iterator_flat(&collection, None), 4); + } + + #[test] + fn count_collection_equals_for() { + let collection = get_vec_map(); + let progress_states = [Complete, Some, None]; + + for progress_state in progress_states { + assert_eq!( + count_collection_for(&collection, progress_state), + count_collection_iterator(&collection, progress_state), + ); + assert_eq!( + count_collection_for(&collection, progress_state), + count_collection_iterator_flat(&collection, progress_state), + ); + } + } } diff --git a/solutions/quizzes/quiz3.rs b/solutions/quizzes/quiz3.rs index dcf2377a2f..7b9127820d 100644 --- a/solutions/quizzes/quiz3.rs +++ b/solutions/quizzes/quiz3.rs @@ -1,4 +1,65 @@ +// An imaginary magical school has a new report card generation system written +// in Rust! Currently, the system only supports creating report cards where the +// student's grade is represented numerically (e.g. 1.0 -> 5.5). However, the +// school also issues alphabetical grades (A+ -> F-) and needs to be able to +// print both types of report card! +// +// Make the necessary code changes in the struct `ReportCard` and the impl +// block to support alphabetical report cards in addition to numerical ones. + +use std::fmt::Display; + +// Make the struct generic over `T`. +struct ReportCard { + // ^^^ + grade: T, + // ^ + student_name: String, + student_age: u8, +} + +// To be able to print the grade, it has to implement the `Display` trait. +impl ReportCard { + // ^^^^^^^ require that `T` implements `Display`. + fn print(&self) -> String { + format!( + "{} ({}) - achieved a grade of {}", + &self.student_name, &self.student_age, &self.grade, + ) + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generate_numeric_report_card() { + let report_card = ReportCard { + grade: 2.1, + student_name: "Tom Wriggle".to_string(), + student_age: 12, + }; + assert_eq!( + report_card.print(), + "Tom Wriggle (12) - achieved a grade of 2.1", + ); + } + + #[test] + fn generate_alphabetic_report_card() { + let report_card = ReportCard { + grade: "A+", + student_name: "Gary Plotter".to_string(), + student_age: 11, + }; + assert_eq!( + report_card.print(), + "Gary Plotter (11) - achieved a grade of A+", + ); + } } From 27860807eb7aef8d7d990ae8284278892c9a5d15 Mon Sep 17 00:00:00 2001 From: BraCR10 Date: Sun, 17 Aug 2025 22:42:50 -0600 Subject: [PATCH 4/6] 23th completed --- .rustlings-state.txt | 23 ++- exercises/19_smart_pointers/arc1.rs | 4 +- exercises/19_smart_pointers/box1.rs | 6 +- exercises/19_smart_pointers/cow1.rs | 6 +- exercises/19_smart_pointers/rc1.rs | 11 +- exercises/20_threads/threads1.rs | 6 + exercises/20_threads/threads2.rs | 9 +- exercises/20_threads/threads3.rs | 8 +- exercises/21_macros/macros1.rs | 2 +- exercises/21_macros/macros2.rs | 10 +- exercises/21_macros/macros3.rs | 1 + exercises/21_macros/macros4.rs | 2 +- exercises/22_clippy/clippy1.rs | 3 +- exercises/22_clippy/clippy2.rs | 2 +- exercises/22_clippy/clippy3.rs | 11 +- exercises/23_conversions/as_ref_mut.rs | 8 +- exercises/23_conversions/from_into.rs | 22 ++- exercises/23_conversions/from_str.rs | 22 ++- exercises/23_conversions/try_from_into.rs | 41 ++++- exercises/23_conversions/using_as.rs | 2 +- exercises/README.md | 1 + exercises/image.png | Bin 0 -> 17420 bytes solutions/19_smart_pointers/arc1.rs | 45 ++++- solutions/19_smart_pointers/box1.rs | 47 +++++- solutions/19_smart_pointers/cow1.rs | 69 +++++++- solutions/19_smart_pointers/rc1.rs | 104 +++++++++++- solutions/20_threads/threads1.rs | 37 ++++- solutions/20_threads/threads2.rs | 41 ++++- solutions/20_threads/threads3.rs | 62 ++++++- solutions/21_macros/macros1.rs | 10 +- solutions/21_macros/macros2.rs | 10 +- solutions/21_macros/macros3.rs | 13 +- solutions/21_macros/macros4.rs | 15 +- solutions/22_clippy/clippy1.rs | 17 +- solutions/22_clippy/clippy2.rs | 10 +- solutions/22_clippy/clippy3.rs | 31 +++- solutions/23_conversions/as_ref_mut.rs | 60 ++++++- solutions/23_conversions/from_into.rs | 136 ++++++++++++++- solutions/23_conversions/from_str.rs | 117 ++++++++++++- solutions/23_conversions/try_from_into.rs | 193 +++++++++++++++++++++- solutions/23_conversions/using_as.rs | 24 ++- 41 files changed, 1162 insertions(+), 79 deletions(-) create mode 100644 exercises/image.png diff --git a/.rustlings-state.txt b/.rustlings-state.txt index 56f1a680a4..f80a4ed713 100644 --- a/.rustlings-state.txt +++ b/.rustlings-state.txt @@ -1,6 +1,6 @@ DON'T EDIT THIS FILE! -box1 +as_ref_mut intro1 intro2 @@ -76,4 +76,23 @@ iterators1 iterators2 iterators3 iterators4 -iterators5 \ No newline at end of file +iterators5 +box1 +rc1 +arc1 +cow1 +threads1 +threads2 +threads3 +macros1 +macros2 +macros3 +macros4 +clippy1 +clippy2 +clippy3 +using_as +from_into +from_str +try_from_into +as_ref_mut \ No newline at end of file diff --git a/exercises/19_smart_pointers/arc1.rs b/exercises/19_smart_pointers/arc1.rs index 6bb860f93f..3b19643963 100644 --- a/exercises/19_smart_pointers/arc1.rs +++ b/exercises/19_smart_pointers/arc1.rs @@ -23,13 +23,13 @@ fn main() { let numbers: Vec<_> = (0..100u32).collect(); // TODO: Define `shared_numbers` by using `Arc`. - // let shared_numbers = ???; + let shared_numbers = Arc::new(numbers); let mut join_handles = Vec::new(); for offset in 0..8 { // TODO: Define `child_numbers` using `shared_numbers`. - // let child_numbers = ???; + let child_numbers = Arc::clone(&shared_numbers); let handle = thread::spawn(move || { let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum(); diff --git a/exercises/19_smart_pointers/box1.rs b/exercises/19_smart_pointers/box1.rs index d70e1c3d39..ee379cae57 100644 --- a/exercises/19_smart_pointers/box1.rs +++ b/exercises/19_smart_pointers/box1.rs @@ -12,18 +12,18 @@ // TODO: Use a `Box` in the enum definition to make the code compile. #[derive(PartialEq, Debug)] enum List { - Cons(i32, List), + Cons(i32, Box), Nil, } // TODO: Create an empty cons list. fn create_empty_list() -> List { - todo!() + List::Nil } // TODO: Create a non-empty cons list. fn create_non_empty_list() -> List { - todo!() + List::Cons(1, Box::new(List::Nil)) } fn main() { diff --git a/exercises/19_smart_pointers/cow1.rs b/exercises/19_smart_pointers/cow1.rs index 1566500716..eb8ebaae8a 100644 --- a/exercises/19_smart_pointers/cow1.rs +++ b/exercises/19_smart_pointers/cow1.rs @@ -39,7 +39,7 @@ mod tests { let mut input = Cow::from(&vec); abs_all(&mut input); // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. - assert!(matches!(input, todo!())); + assert!(matches!(input,Cow::Borrowed(_))); } #[test] @@ -52,7 +52,7 @@ mod tests { let mut input = Cow::from(vec); abs_all(&mut input); // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. - assert!(matches!(input, todo!())); + assert!(matches!(input, Cow::Owned(_))); } #[test] @@ -64,6 +64,6 @@ mod tests { let mut input = Cow::from(vec); abs_all(&mut input); // TODO: Replace `todo!()` with `Cow::Owned(_)` or `Cow::Borrowed(_)`. - assert!(matches!(input, todo!())); + assert!(matches!(input,Cow::Owned(_))); } } diff --git a/exercises/19_smart_pointers/rc1.rs b/exercises/19_smart_pointers/rc1.rs index ecd3438701..203884a0e3 100644 --- a/exercises/19_smart_pointers/rc1.rs +++ b/exercises/19_smart_pointers/rc1.rs @@ -60,17 +60,17 @@ mod tests { jupiter.details(); // TODO - let saturn = Planet::Saturn(Rc::new(Sun)); + let saturn = Planet::Saturn(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references saturn.details(); // TODO - let uranus = Planet::Uranus(Rc::new(Sun)); + let uranus = Planet::Uranus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references uranus.details(); // TODO - let neptune = Planet::Neptune(Rc::new(Sun)); + let neptune = Planet::Neptune(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 9 references neptune.details(); @@ -92,12 +92,17 @@ mod tests { println!("reference count = {}", Rc::strong_count(&sun)); // 4 references // TODO + drop(mercury); + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references // TODO + drop(venus); + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references // TODO + drop(earth); println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference assert_eq!(Rc::strong_count(&sun), 1); diff --git a/exercises/20_threads/threads1.rs b/exercises/20_threads/threads1.rs index dbc64b1644..63280ad4c9 100644 --- a/exercises/20_threads/threads1.rs +++ b/exercises/20_threads/threads1.rs @@ -24,6 +24,12 @@ fn main() { for handle in handles { // TODO: Collect the results of all threads into the `results` vector. // Use the `JoinHandle` struct which is returned by `thread::spawn`. + + match handle.join() { + Ok(val) => results.push(val), + Err(_)=>results.push(0) + } + } if results.len() != 10 { diff --git a/exercises/20_threads/threads2.rs b/exercises/20_threads/threads2.rs index 7020cb9cea..6f8fd96e78 100644 --- a/exercises/20_threads/threads2.rs +++ b/exercises/20_threads/threads2.rs @@ -2,7 +2,7 @@ // work. But this time, the spawned threads need to be in charge of updating a // shared value: `JobStatus.jobs_done` -use std::{sync::Arc, thread, time::Duration}; +use std::{sync::{Arc, Mutex}, thread, time::Duration}; struct JobStatus { jobs_done: u32, @@ -10,7 +10,7 @@ struct JobStatus { fn main() { // TODO: `Arc` isn't enough if you want a **mutable** shared state. - let status = Arc::new(JobStatus { jobs_done: 0 }); + let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 })); let mut handles = Vec::new(); for _ in 0..10 { @@ -19,7 +19,8 @@ fn main() { thread::sleep(Duration::from_millis(250)); // TODO: You must take an action before you update a shared value. - status_shared.jobs_done += 1; + let mut job_status = status_shared.lock().unwrap(); + job_status.jobs_done += 1; }); handles.push(handle); } @@ -30,5 +31,5 @@ fn main() { } // TODO: Print the value of `JobStatus.jobs_done`. - println!("Jobs done: {}", todo!()); + println!("Jobs done: {}", status.lock().unwrap().jobs_done); } diff --git a/exercises/20_threads/threads3.rs b/exercises/20_threads/threads3.rs index 6d16bd9fec..b72dc26d84 100644 --- a/exercises/20_threads/threads3.rs +++ b/exercises/20_threads/threads3.rs @@ -17,21 +17,25 @@ impl Queue { fn send_tx(q: Queue, tx: mpsc::Sender) { // TODO: We want to send `tx` to both threads. But currently, it is moved // into the first thread. How could you solve this problem? + + let tx1=tx.clone(); thread::spawn(move || { for val in q.first_half { println!("Sending {val:?}"); - tx.send(val).unwrap(); + tx1.send(val).unwrap(); thread::sleep(Duration::from_millis(250)); } }); + let tx2=tx.clone(); thread::spawn(move || { for val in q.second_half { println!("Sending {val:?}"); - tx.send(val).unwrap(); + tx2.send(val).unwrap(); thread::sleep(Duration::from_millis(250)); } }); + } fn main() { diff --git a/exercises/21_macros/macros1.rs b/exercises/21_macros/macros1.rs index fb3c3ff9b3..fb550f9d61 100644 --- a/exercises/21_macros/macros1.rs +++ b/exercises/21_macros/macros1.rs @@ -6,5 +6,5 @@ macro_rules! my_macro { fn main() { // TODO: Fix the macro call. - my_macro(); + my_macro!(); } diff --git a/exercises/21_macros/macros2.rs b/exercises/21_macros/macros2.rs index 2d9dec76ca..a56d137f36 100644 --- a/exercises/21_macros/macros2.rs +++ b/exercises/21_macros/macros2.rs @@ -1,10 +1,12 @@ -fn main() { - my_macro!(); -} - // TODO: Fix the compiler error by moving the whole definition of this macro. macro_rules! my_macro { () => { println!("Check out my macro!"); }; } + +fn main() { + my_macro!(); +} + + diff --git a/exercises/21_macros/macros3.rs b/exercises/21_macros/macros3.rs index 95374948be..acda10b4b8 100644 --- a/exercises/21_macros/macros3.rs +++ b/exercises/21_macros/macros3.rs @@ -1,5 +1,6 @@ // TODO: Fix the compiler error without taking the macro definition out of this // module. +#[macro_use] mod macros { macro_rules! my_macro { () => { diff --git a/exercises/21_macros/macros4.rs b/exercises/21_macros/macros4.rs index 9d77f6a5c8..3396f0d3dd 100644 --- a/exercises/21_macros/macros4.rs +++ b/exercises/21_macros/macros4.rs @@ -3,7 +3,7 @@ macro_rules! my_macro { () => { println!("Check out my macro!"); - } + }; ($val:expr) => { println!("Look at this other macro: {}", $val); } diff --git a/exercises/22_clippy/clippy1.rs b/exercises/22_clippy/clippy1.rs index 7165da4559..ebd63bf284 100644 --- a/exercises/22_clippy/clippy1.rs +++ b/exercises/22_clippy/clippy1.rs @@ -3,10 +3,11 @@ // // For these exercises, the code will fail to compile when there are Clippy // warnings. Check Clippy's suggestions from the output to solve the exercise. +use std::f32::consts::PI; fn main() { // TODO: Fix the Clippy lint in this line. - let pi = 3.14; + let pi = PI; let radius: f32 = 5.0; let area = pi * radius.powi(2); diff --git a/exercises/22_clippy/clippy2.rs b/exercises/22_clippy/clippy2.rs index 8cfe6f80a2..fb12f983d3 100644 --- a/exercises/22_clippy/clippy2.rs +++ b/exercises/22_clippy/clippy2.rs @@ -2,7 +2,7 @@ fn main() { let mut res = 42; let option = Some(12); // TODO: Fix the Clippy lint. - for x in option { + if let Some(x)= option { res += x; } diff --git a/exercises/22_clippy/clippy3.rs b/exercises/22_clippy/clippy3.rs index 4f78834918..9f527be596 100644 --- a/exercises/22_clippy/clippy3.rs +++ b/exercises/22_clippy/clippy3.rs @@ -4,24 +4,25 @@ #[rustfmt::skip] #[allow(unused_variables, unused_assignments)] fn main() { - let my_option: Option<()> = None; + let my_option: Option<()> = Some(()); if my_option.is_none() { - println!("{:?}", my_option.unwrap()); + println!("{my_option :?}"); } let my_arr = &[ - -1, -2, -3 + -1, -2, -3, -4, -5, -6 ]; println!("My array! Here it is: {my_arr:?}"); - let my_empty_vec = vec![1, 2, 3, 4, 5].resize(0, 5); + let my_empty_vec: () = vec![1, 2, 3, 4, 5].resize(0, 5); println!("This Vec is empty, see? {my_empty_vec:?}"); let mut value_a = 45; let mut value_b = 66; + let pivot=value_b; // Let's swap these two! value_a = value_b; - value_b = value_a; + value_b = pivot; println!("value a: {value_a}; value b: {value_b}"); } diff --git a/exercises/23_conversions/as_ref_mut.rs b/exercises/23_conversions/as_ref_mut.rs index d7892dd490..b0157ecff6 100644 --- a/exercises/23_conversions/as_ref_mut.rs +++ b/exercises/23_conversions/as_ref_mut.rs @@ -5,20 +5,22 @@ // Obtain the number of bytes (not characters) in the given argument // (`.len()` returns the number of bytes in a string). // TODO: Add the `AsRef` trait appropriately as a trait bound. -fn byte_counter(arg: T) -> usize { +fn byte_counter>(arg: T) -> usize { arg.as_ref().len() } // Obtain the number of characters (not bytes) in the given argument. // TODO: Add the `AsRef` trait appropriately as a trait bound. -fn char_counter(arg: T) -> usize { +fn char_counter>(arg: T) -> usize { arg.as_ref().chars().count() } // Squares a number using `as_mut()`. // TODO: Add the appropriate trait bound. -fn num_sq(arg: &mut T) { +fn num_sq>(arg: &mut T) { // TODO: Implement the function body. + let v = arg.as_mut(); + *v=v.pow(2); } fn main() { diff --git a/exercises/23_conversions/from_into.rs b/exercises/23_conversions/from_into.rs index bc2783a30f..a0bdbcc6c8 100644 --- a/exercises/23_conversions/from_into.rs +++ b/exercises/23_conversions/from_into.rs @@ -34,7 +34,27 @@ impl Default for Person { // 5. Parse the second element from the split operation into a `u8` as the age. // 6. If parsing the age fails, return the default of `Person`. impl From<&str> for Person { - fn from(s: &str) -> Self {} + fn from(s: &str) -> Self { + let split: Vec<&str> = s.split(",").collect(); + + if split.len() != 2 { + return Self::default(); + } + let name = split[0]; + let age_str = split[1]; + + if name.is_empty() { + return Self::default(); + } + + match age_str.parse::() { + Ok(age) => Person { + name: name.to_string(), + age, + }, + Err(_) => Self::default(), + } + } } fn main() { diff --git a/exercises/23_conversions/from_str.rs b/exercises/23_conversions/from_str.rs index ec6d3fd129..9d3f3a67ca 100644 --- a/exercises/23_conversions/from_str.rs +++ b/exercises/23_conversions/from_str.rs @@ -41,7 +41,27 @@ enum ParsePersonError { impl FromStr for Person { type Err = ParsePersonError; - fn from_str(s: &str) -> Result {} + fn from_str(s: &str) -> Result { + let split: Vec<&str> = s.split(",").collect(); + + if split.len() != 2 { + return Err(ParsePersonError::BadLen); + } + let name = split[0]; + let age_str = split[1]; + + if name.is_empty() { + return Err(ParsePersonError::NoName); + } + + match age_str.parse::() { + Ok(age) => Ok(Person { + name: name.to_string(), + age, + }), + Err(e) => Err(ParsePersonError::ParseInt(e)), + } + } } fn main() { diff --git a/exercises/23_conversions/try_from_into.rs b/exercises/23_conversions/try_from_into.rs index f3ae80a9e4..c0b7f47451 100644 --- a/exercises/23_conversions/try_from_into.rs +++ b/exercises/23_conversions/try_from_into.rs @@ -28,14 +28,35 @@ enum IntoColorError { impl TryFrom<(i16, i16, i16)> for Color { type Error = IntoColorError; - fn try_from(tuple: (i16, i16, i16)) -> Result {} + fn try_from(tuple: (i16, i16, i16)) -> Result { + let (a,b,c)=tuple; + if (0..=255).contains(&a) && (0..=255).contains(&b) && (0..=255).contains(&c) { + Ok(Color { + red: a as u8, + green: b as u8, + blue: c as u8, + }) + } else { + Err(IntoColorError::IntConversion) + } + } } // TODO: Array implementation. impl TryFrom<[i16; 3]> for Color { type Error = IntoColorError; - fn try_from(arr: [i16; 3]) -> Result {} + fn try_from(arr: [i16; 3]) -> Result { + if arr.iter().all(|num| (0..=255).contains(num)){ + Ok(Color { + red: arr[0] as u8, + green: arr[1] as u8, + blue: arr[2] as u8, + }) + } else { + Err(IntoColorError::IntConversion) + } + } } // TODO: Slice implementation. @@ -43,7 +64,21 @@ impl TryFrom<[i16; 3]> for Color { impl TryFrom<&[i16]> for Color { type Error = IntoColorError; - fn try_from(slice: &[i16]) -> Result {} + fn try_from(slice: &[i16]) -> Result { + if slice.len()!=3{ + return Err(IntoColorError::BadLen); + }; + + if slice.iter().all(|num| (0..=255).contains(num)) { + Ok(Color { + red: slice[0] as u8, + green: slice[1] as u8, + blue: slice[2] as u8, + }) + } else { + Err(IntoColorError::IntConversion) + } + } } fn main() { diff --git a/exercises/23_conversions/using_as.rs b/exercises/23_conversions/using_as.rs index c131d1f32f..dff453b202 100644 --- a/exercises/23_conversions/using_as.rs +++ b/exercises/23_conversions/using_as.rs @@ -5,7 +5,7 @@ fn average(values: &[f64]) -> f64 { let total = values.iter().sum::(); // TODO: Make a conversion before dividing. - total / values.len() + total / values.len() as f64 } fn main() { diff --git a/exercises/README.md b/exercises/README.md index 237f2f1edc..3dc99dc37d 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -1,3 +1,4 @@ +![alt text](image.png) # Exercise to Book Chapter mapping | Exercise | Book Chapter | diff --git a/exercises/image.png b/exercises/image.png new file mode 100644 index 0000000000000000000000000000000000000000..ffd37092ee5bff7a62fff2575b3cec28510cf6af GIT binary patch literal 17420 zcmcJ1XH-+&)-HA|2#A1m0TmFX_mW6gI?`(dM7kJyj|Gq-M0zIz(o3ZGM5XuMLMK3g z&_hi~xcI)`x%YnGIrqmMU#+}Mqb>~_na*M)fA+e~C+_sL!;%ISHhZO+)fx;r|1 zbw{|O!QSZxPtaxZpgYg=`9rz$zdfh^UP#Uz>KbpM7;d=yF^fYnho^~~yiWBRwLE6% zBKhT*XXMwf2Yt8FP7li3dIQ!e>uo1IT$W(-7UZ0(%Xk zi)7kr^YvT-MrLGWUrWD*Mf)(fgUxi3JPn2^eKJ&taYeGC6lyEQ7m;xZNA z%;8$AqDPOZPDF!cqS|+B@3Q8ZA3ZDoF+rw1^^bdkK&c+U)h)t`O4fow}6O z(jKgy%g>NoXf!2nJs-jqKeyRinj>Nb1C4I(M0=T0nHrGPH2ySvD?>2w&_cP(AC1}d!hwAWMo?O7|Z(M>z;P1{e8C&v=eX)r)t%eiPU8I+Dnh`f=u>z>E^T$OrzQc znAU#88jG;YAzLnej}y@lvlXtX3r3}V*DacLboQ+|nrPSF^TcIUO?|E#O7&uG6y-l$ z1=Z}0yl9|%It%+g_P_;#8NTjJ~=TyTGT9No9nDN9j$Wf=!nnTF0AzN z(tNT-e{7d?58g*3p7Ej&Q+qmsm3hI4O-C;<#xOYqp8_ zU$M|7H|9LVVQpGMeqa6 z0uD7<^ALVkmfPW^ojxx1pxtdrFE95L)gpb{hv^{E;=ydl5=)}dX?xC*?z=|crVmt~ zW?z{ak~!S>7t@=qyB(pbGo@MS&@e=k(OzVChptgtuR`~L;(!@ zl%D@wXEIyjnAXB>7pkYpB&MMNS<#yDoVnC*=yPjHOA-Ntfq}Mg2M;E-h9Obo$*lVu zlKZs1+Y4B9WysJz=N!==(fRFzvlBjFBk_)xS}GmvYvLWIyq7aCdJcDwtgj0ujID`S zcL&G$Yo@xn;X3B$$1+)OMPE5$ntrsEXrnUJeX9^@SS({kqNQMxaCd_BRKfU~zGe&4 z zogFb*zX&Hh%q3m^EJn|oV38N=cctpqp&Os>4B%iqxaYG=taqD@p$xH7z=Ajv^gU}N zPOmB5@8bMB?Il+0=RiiEs$UTf?J2^L@WsY?$|tSKAL`4}w-b zq|!CDLM`Bloy$SB2e;>l&P5fv^r9ZK8ib+_ zweo2Mx(3hxa8E=sNiIVoZApQmvf|6XtGn1J_r7$OdgJZwXTxLe#OP`-eWT0oayfhW z>swc+C|;cWYxILl>|YFDy;md6)m@eUQZK|3&3pG2yYg>uAHPX$+k5g8KdYS^`J9bB z)>&;IC^C#yC#TsM%il6yGhR}H02A^lf@+V);oT18>@8oD6LkFd+Uc6cL+hLS>;&$G zn!P=gOdhm*eUDmJ+z;Rps93FFjrLZmY$gi+KB6q^-R;fOadXCPSf<+2U_t`uSSAjd zk%wNUX|Eh<2eAi2xaF+UYz8%4Zo{cf4RyWz^w&!ME{-^HyC&RYbz(T{GZES~ zN)hAt)WE0>&stFF%agM|%%kg{UM6mpnhyhu_1wGY=RggfzK$WHwLsvZ>m0YI8 zs571duuyjkSDW-@$UE)@C>_KXV8nwf&ZffzTU5uUW}sqVf`yvw7VJ zoD@TIGjE&x^e`m%nVYRrf4H=Q`?$D&RqqKp!iR3V^g9411>~5@ECV;{k7-KcPF_!& z+_Lx~E*8Pt6KOn2+W*dj{EiCT&LXW8CIjiBq!UelE-T*By}^BIF zV{qGnV%p|1Srx@!QR503S*LvTrT;M>zlXHdo#lSf9Zs27zDt%b`qw1NM@IIXE}Z$i zG#qH@kcmP|l;-z*%|h#G$I6f$YWagXd7MeAa{;mu@-m`23?Uuf^f$E@_-gLr7W z;~4tUv1TdVFRM$>oIhbR_AS8|{>MZA?@JO7bbM@1!(zsi=lFpQeAMgZ*dZ9A8gQN_ z{h&0S$fI|SQnyn-3%FRld)z#Uv8{=bZM8VR;lw30&Op)k^vK+t`)q6IfRIXAjacZF z8U5?aVBI`8+;%H)90va)c%ZV`*LbL}(yF^nOGfryR^Wp5p)DwV?pI?Bk5msbrRu1O ztTFNu*?Y$BgPe!g?M<(a2Q(%L&MH9E8mg9*#vRuG`q_{3Nf47peDHm5=3#k)aQ_N( zYS6R0WMuhk(}Yrux{AW_Y~Um9LyK5{uNlYV=JPt{pS;d=%PQw=zmhy#TNcU1+!=-! zxeJjvZ~x1m$BlKHzj}8t=H%>rGl8KHs@+`5Er|1h0oVMUJtXrBMppd8yvfdGp?A&lO29cEx$na ztXzAE?B7@}9XfOjW~nnjEo>gd>y8%sHYRCVRyhNuO&~wP^XY%=)rtp886hECLhE4NZl zPk}S83o^~XuFo<`&AxYHMc}L^79N(=)Z}Aos3m7VFfe3gfJSj^cIXlZeOZ{*lviaw zi;6T`=K6oARw~8)dwYtq$ii$N4vA9LDS9TQCA${oPQ9wa3@sHV7kwIOm{s;WhpCg> zqV%(!7s%ef&g}SimV%=*7aYSP*QuDAbVhaVTaE6oQuX8ojP9QlujA<7U)}zJ)=uY%Xw#W~Np&Q>17&0EdF=mt2Ke`t zBbs%EQC4yyS_g&BIzswQK*uS6`QG6Fgqy}hqQI4_R4Ne}7wp|CiC^qk|1)ExZ=L1I z{1la)dvvWZF?;hfYK$#k_CGODY8Epr@9sv?U0JjvULX{%AJ7u~H^jd_{!a{4p`ebm zs4VJ|&LeGO=ZN3nO475<5C8sA`oAak|Ic$R`twy%++jy8Ufsj1MaMhpm=mY$Ev}@` zs<7+tEsw7UXkOHc_DX{ow}O>yY`}BwF@`p_$57Jpaxu{wwpjWqwND~jR${DQQ{}Z8 z@idMDCFF;&&Y;(Vm=`8bc-&Q+JxxDI92#NHi!$B!kI%qCi`Ky}es>XkkQAN;`|pbn zFo|{9G#6Vn)2uUk?$|0t3xiq9HS}4Ow$>^fqjlXWkPK(p2mE^88BpBs3!&yJgK55{ z>P=_ZS1u7nJz^>TS&AT&4*Mz-ec$Qmcx&m}DTHV~+I77~PzA(%5)G_oVc+OgRMJM{ zH?mb;Q>2P$KYXxsuL!P77WJV3y}-T_{9yvqJgl>feVJ#;2_Kn|sM0so2w03^wbB7xd)SYY3yBysR}uI)Mv3x zxbuWL5r+3_;hK4ZRA&#T;~Y=Z=tEc9+x{ku@j?KmXQOouS;Km1Vq9w@g-0e0)bi(x z;~oI4!Eg4>))d=bu$^Hv(O}i6ID3M%6K$tWSQRn7CJjt+M3Bh$ zVPceh?3{)fh@zpD8OgF*(5swrE)E@3;=jNztjsUAv*~Xq54f4M#1kHCrh<37F|_iy z`4O+)0B5jsN)p0+6)07 zTWL9yJky8#3V1$ASzZlODX47>*dC|M@P7b9OB__WWYE@TkON^yjZ{_r%V$TU2Wjb? z0bMXo$jVgh#-@v(aMpN4HQ!=CeH#)pJu_(kxWphm-aT;ihw^XJ=vl2Kfto}>)fD^|Ln#F#3J~}8~P-Qf=eA2(fPUJ(My!O<4VZX@l za;@Qq$L~j5TF8b{xHS7Blu2eY4!_^tt8S~@EvZw;NyAvSj}Cw8T1omLr>jLv-+C(Z zEwjSD(eP~eu*|XiG}*;v3HaD`grtqvk}iGXq!}mm#s@}r1$)qA%eB`niIk?Jo7 z+2zq4OfLlq@I}r}coW*P_?GE|f9|S8l)G%34m3pbavqVd4T>7Qv1gsQN&~g;4QJlQ z%@B%TbcUbSe!7Moc{iVOJT%0;z@f6nHRaxx95ZB!{d`qYxJyj;_G`d|P%WP|I`G|k z)uX36E$wD9{dwv1R!4Qk2lR<!(pSOfkvzKpW>3i>IA4BXg4* zAP07_rzB6y=J{L}n7ym-D4jk*Q!bKeyul4u9Yw(`Rjsvx(n_@G7-WdhJtXe?S@i6n&# zH;yc*Hy!(Ylbu_u%e-Vp`2@sLA{aLd{NaQ<5V{#jzOUi``lQ17$k)g9=#Mdv8>r9|&n!>|_@n`vht z(pzlCIH*?Pjc~OQFz-jstBuw3;X4NOlKr`7D{tjr>Nuzt?%W!=Q_Q#fM)eskv7z#X zWX0z3GTt0t<)G(DFi818am!(zS&UE|j+Tf*>-`NH8P!7D^ZN{w4W9jt7s*f5BLGfs zOhW9dBMYysway;+cY`AC39-h(llY{0=bFb=cS))rq`PQTOPm>U==e zgTKM8>R+`XtZZ;I_5qnDU>r#|s@t336Ps4dJZu>uEWGDTQ2Z#xS2MfNfOPioR2^5g zP|dL=@bj(R4OfBRJ@9hjN`=L6tbD-xkK+$y_k=6dBbmyDDxQ8(%ZG(*^rVFc#3y46 z`uiix#shy`pYvh&g<)yDPQ_R1YOY-==bH(ke7P~@nZ~K;&1#2jRX9Ce<%5X3cAYWy zpAsE>-*yE%Cm{O19qN8#5gfl}{Hz(T)Y&CsKPMmq0H#}3tg4g@JM*WPUx4jQkg z(SVP4FU12iGOPNpRy(6T)@t?& zZtptIg$dz7Jqr1SP13lvo>avKajV!Kuc)uP^Cz~~GFFn;L2w*}@;0hum6Q*lyG9P^`+G!3&;H9!Mfytq<>pzvoA&o|R1O+Zpr%%ukYIFzT^)&yL7a-lonHm9^0 zjQc!z2#T}I&Ago-FnC)mu5i*f|Z3!bJcl< zt@D3_a|<;ZBZtB?u{Wmn*XDX6J>Cx3lJejU!XDGu34hLVL4Ls(Gv$8Jt8~B#o=8-{ zxBR)v&U6{$ysN~#C`FQhT-@DiJt$D%#G^Sg0Hf+WO&Ky>>_2xq{Zk3i zp&5^vDu>^)_v6SyA(8%vKYyoFW7MAXUrImu6a0;z!=m9K_`332gIEg1cKe9SduaF-?u&$;IX7AJUVFFgGz)^uiW4WpUkZkie|$iqfnOJ>5G=>gG84&2A=2V^<+1-TW6w zuz>%B=>piEiBLyvRUa+qXSGKoo@tzN%@{2XiBcV=qqEy8UR`_OO`m*p)6KK>js3{l zcul{#ubE$9!ho8i32-u3xG(>90MN_ODI}yj5-5PQZJqg2y>Whr8iVwPyvIMhy&fK% zTtw3%`x)lEJPU3?w`roW2l;tWc*N>Sv{%w;`$>?xvn6$6?7NLTFNNTK4hJC5=ES>z zucMIV;m`;#{opHwUrf0YZWp+lQuIrOZz2ay9ET&hmYYwPo+Q>5H>5h_)TdxwhJm5I zYEwA5*u4D@GCMKBJyAnj5Ute^A?l>}l#~T$rQH3u<#wkRO(l`He{5;IzjTO{D$}q{ z8$3Pu&=gpG*@2Z{!S;i8pe?4bGInWPQvO81~A^? zbwNxIwI|1qV*v{@1KroORu*}$kmVO_9H?s0i4oipVq9BDwkpprz#Gw3DISktyxe*Z zSyBiw%L|fbXGM{Xz29d9?F&E3J0F&0tIayR!EHZ#fB7!}=&yyY zmUWT$g^!}i;|o)@in3p9q_RFUPlPZpTW6=R4sr~VKPW4nGqh@V#B>y{x7=y79Vs=7 zLWg(X!Vk$u>6)LE4-q>xXKek)kJ`S!^2)KrN@7azo0nCxR_l+u)*vk=SGNH3h6ZW7NOln{1OhfS8GpQP~7Vfsj{q?HwC|s#bJ*T!_P!L+~?iIZi zJ3?lCmOryIYfGU9_46)zI#!fmz5D3=iia%GqzNHlvTeCb8R@X+U343Lrt|vO=8h`c z^AwH;wOK-+N^9R&9D{A*Rb~e`O^!L8P=TyD=S1<#CPd{|zbD}z=|q1sjmi~=HyW3} za?Hwsj&tj9#(}T*a8Wp$RX(RCwFi^DlOe1v$_WDlE3pU&w~k6s5;B%%g7Wk)7McqQ zD;rx6Wk$*Dl-_Ito)Q$loh@b+tZwe~3$r}!`iunbuQH>+K2!7!Q6rcLGZ5yNDb5N? z^5E4Hx#eP63HK&*Il2d$j|4hmb88x%WeX6J2ZWgE0;X&t0$^PGhoby zb`Z%}5eU+0=ha?1<|uNwPaq0<)2hr;$sco?Y>WaP7B&Y)i^~rR$e^=&^SmqbZ|hLA zUK|vtR{W5Z*_Zc9FSa9;t*k|9X~Kr}i6SPcjOR`S)EN_P90z?T67S<46^u4vqkVl7 zj4e_NYsk@v(CSbNi_|UHdhmuoP7JS2KP;IpDs7M}8M=2}XWu9#pNE@oxf*HynZy># z48M;!@3W35r1#6JXz|>$X_>6nx0||IQX6zWoOi>1GwC-2VT*%VQi{rv+S!&)5f^># zI0LSX8#=W>jj}jgH-stavfO$m?uNCFNBv!?SCHc@w;-sahu&8WMdkZz2fp(A=bU36 zKlI?6;dQ@F({z*8GFDdAkgw&j!>NRQ{0Xb2bAUptu!WpVw7wW?oyjDwTstRPT!fB8 zz)t7x3bjC7{%t>{S)*{pg(OjTT<5ar_od0BDi>PiS?%k}ue7tZda{+dbyyC=8_&Bx*aZ;0tN5o9 z+)&-IU{(swd&Y~WkYUdZ*=qN9-&*M-D^DsNTf!z*nGt~~5z4p@b{vU(n%f2cp%vatN`dW zEqf4E?$&#X&MV2++ON;~q6IFWX==7aay`^kL{<3+uC=I8dQcd;iTPKoF zZT4&tkzu9}yk9qLnkr=@&Q8!nGcwK}#IR#w2g4Tmh9`lNyhpUe+Ea|QYoXxb{<DVY!!_!!zZr-ti2H5H9lnwiI4WBXUFTDe# zgl1gJ3uJQJZ;fW#C54JG{l7#oMVXUQqIw=m*7svBJuX@!Tp%W1CG>wHba%hC+44hX z7Wb*CEmqtPocPW$+sa2R8ANBv$>$pqH57Gyj#ts-LHNY^po>8BLn$)JX;tH{L%!dB z%J`|=!ZYG+8K~usbK}UGxC?0a^yqp)5vb%;<-`eXpQlHd!^MZq7@p#OdV8LcP(&cm z=`mN3fCtDNj3yzq^U~54`$~xz5C7vQaIYh~n8#7g$B=uStpPJSe29)tRByzJrn(S> zq0<|H8!YTdBAlG7sm|p@S-^JhL{MB^1gtuST0XQ9Jq|7mW&a4_R<>^sf_KQ`oYSNp z`#;0_^2HyCI5%3gKNN8p0qaDVS$2i9`e$SKU9Sw%GIiu^Ex9L|H6A|hiIBPw%J^^*KgNB zB(;y0FrvA3T}N^We^WpvHa;JxGkwCb=esQ9?bz4JBUh{o^RQ0@P{wvPJgEm0u~Dw) zIyR-Kz}c`4n zvO-z;pQf}U1_c(glsBj%*#Y&frPOK$(8M8xAojG*Z!-fsKiuT?Nn|FXNr4G4JV_Wz zw$n}JSOL9b-S_e;`Qi>i^lo|pVZ-;H0%0y@mAH@Lmh1%~XA()&rd#<*d-Cwr@(f3A!@5$n%rP9c8B-C2MkcqHLDw z24k5b1DwHxP=72;|C$BFILS4?W6N7n-vr1vwcAL(8EjB>Vnn!z`+1BwBx=FiOJOV2bJfGqFC#`h}SC*?_XSEVWD{ zhGzk~LOlhU!dsV$I2?V*3LJjqwf8!maUUjCShCYV@1QuY8}@S1avV!bIo0O~){z@o z6>M{y&y`;9p<%08@?%li4qNz@u)AISkOrD#kFy$gM~G~0UeS3NMD^0uVYkP8_6!l1 z9$;gSLsKU{X{sM;=Vudh*I!FwsTnov+f=|=>qS4aT_MtW68GRFx;U{1>Vf#-KJ{SF^0C^C^GkfCA*~Mmo02 zs2XJdzSQ!2P5L6TDYJmNOvl{`vhMeJt3bG4-Npc#QfFxM;5W9_&8(T&Ykl=8FGB_K zO{qbAAm?6tv}J;jAvq7A=EO_C$lwI&A_)>SXxL=CVM0*$7)ibiQgUBfj``B8!g}1v zpHXM8cFJtSYKH2cZmjJb6BH)(h}{L9Ej0DBewl1^dL^rT+Ng7=C5b!8*)&CgdHU&F zYS^46`^WliTVE%^@`An&<)e+QIdn%E&QkTNUoWLKn=s*BOd;NOoR}}S455Ie6v)wh zeq~2Rj&k-u#8nf*$8lc2bg2J^Pt&6Hluxf8OLi$|zemV;suQ*outlv3ZW74C& zu6Xf-0(h0p{mma1>jZnL49I`^EKXxQIVEM`VpPzscOd?IAHDb~T|c3f|NZ5Tk02l1 zasr^QxY6S1`gVu~z*~rZn@kN`?O`i}?S4(?)luA}dHQQ&P|);D`)EJujC+R`fb1Peh9+!RvI-b&OPYz`})0 z0%@$-R%I6RN1phX|5-w_GGn=1Ls$Yq|1EFnENT$fSNt!2Pm~`!^&3!TW%e73l_2b} zf&XI83|hb@C1TsN%PePWEZh}~$n`p*0}}jGljHGxNzN)`pfIWZZPmA=aZOJnP-?hY zgNXdVuD=$odxuRRp!Z(1e-wIT9N{zOZtl9?u!5-m zW@PB^|FT&H_lN1pK^?VzF6y7{M2~oBJ=rkdjrp*VbW@fRp&D@QRn#WDI2MB0pA=l-#hR4pc=`<09E9E0-{cqd6T|VrtKpdR2 zDF6ld9;vn=6EXWa?$92qNzPu%@6VJ@n4iCF>RC*#NfcYTyX8FQtp5r+D$71?2QZlE7j@|!HM!Y8Lx@Iv;{>%7h60aUujjn$?H zCNzZ2irkt`_4(s$rOBG3r>ip^@zH)k`>TvkDh2@je+`pLeB2|_p68F7rd&>or2Pgg zzJ6*zYxu!|No+#LQQ4iTSBrurlaILh!cTmjDcJrk1s*-Cj6ctI{9vLy#?fqEJFn6i zZ=2D8Oc)>f3tAE7rK|EWkGO)s_WNyZQ{r?U(KoC3I~rk%vo^aW(+axs)A!msiMxlY)qBXxfji72+JEDOvqnz%L)PIVh8~Q|vd)V5_R? zQo_c#)WbccF8TH3Rm7T1mc@6w_MA!=cz{y6CJEQta*BSkUax!j)B~iy>+yb!&3Gb> z+oNX>aN))nHo0GODbeNzq?oG`zN)0z5|~?@Jx^MgiBYk2W}SaYV%xnd z`FHZzXV`Yu{G4y=JnfNJk1$Uv&-(1P+^U(HOq{OhF76+?ub^LmyVGBTNRY~S_H zO#j}CJ6?i|e1RxcWpeiO-2&|e93c{^Dc)b}3Xt}YuHOJc!_VKXp}YPo+4|9fM>xqS z^o?*2B}mlno7K&mXkOQTf2O>s8*Bsh8Qs?7ce~JWu^RhQD3LRn*PAO!Zd{K1dyAF`jDwMQ;<_B(JX>W1No zv}8_dg^=A(w%$ZGqvI?l%S)daB|i58iWbkAC5h2VWA;7^z&@e>A7oO-pNZq(e$zxl z#&&bv;9OI>+2I6EsoF(XI^@f>aVZXMbNnrS{ckbi||T zfK-$v@e(iK6Yg(@h-Rigyg-nVA;ebY9>vv5(cLB9JTL!|2pYY{o)E6X?mNAj60=AQ zG(Fc{=W(`(7CQ?|cBAxq@`)l2rOp|$#eS(*52hle=kP^rq*3`tJGDB7xt!Y+%xP4ig6*Cf|*_{{+MH$s-Eb%se zlUGg#qjsgqg1=~*1yz4@!*n)4e{}qU3FME|yQRje+UyiNPMaaVj<{n1!X61bvpn5^ zaUETEHcMDJ={(4((&8KdY)UC~=;AfEcH`syyzb#mjMmb>U-SKcXsVwr7_!Z|{dPO< zDYH~nrrudk3p3x|oMJ1=2AI1Osw=bY#NVuZZR`7DT6ENoG;n=&6#R0K_ynl>)&Sx1 zx$oB-n9M!go!F754M%pbc3JC&@@KAv<26#JJ7YZ@1|aMC)xXbz*@XoQerUi*z0*dR zs{tzze7&@8ds9@e%Pns_&&@UAy-{tTVFLU4c8^sF=V+aIu=QK{kM%P@GfSq8Y*tsp zK3(k6%6L4>rB`2jMsm$*?sW26oMH6{ky9+63$8D2oJmU1LmrrP zTE8%WaLXC-sd#7Rx5$Oe=Nt~JhS$fSswfe zeE+0KugMV?tqgcri{Ka-)2R)p2$28dyke#x6N1S8q5U|+xsMeWCFTvybZ3X3d1vp< zbrw{__MMSn!A1zmA+U z@z}~J{I+NQj`nPCrC-M$_4{IPI<0K_WnlJEpk-g_#FI!VBL1*W4eHq6@4&u~SDN+i zh>7J%k=MXSep+ary#ZC5n&|HxvfX#rI*jd*RwtqT<#Vflgm%A>I2>-sTrL@4<=UHH zz>@m7xjQsM&5j3u3$Lqq8HSsxn6AK(nRvKABY3y8@W-=wFt%lgeR+;~r-om~#UIN% z%~s*zB8C34K5*pb)%wP+cKY}d&d*@lLpfFaJeA&@s)WZi89cbVdgBOzwFe6oVAM|J ze0w4qv-zIPyK;o^Xi)C{iKs(I`lA-t@`>zyCCYGFSmD@8PMWc#OLNt=1X9MAYL@~` za0gM{R3($x>}H|c{!pS{2HQP{7%(3@#c7!y?Oie?dbtigyqV0BGjo=)6KOIU?0jCW z6*y%JN#^SN`-p%X<{vz7XBYrqZpD1_a9{elTW-JDo^SSWB5BNQs zqG&asW9113-8za=`f;(8@_r1uNhtn*=GYI8IOVmUUOtRhYu6E;C3mY?VSd+$%c=f# z@Lw|cZ`0OZ-AUO>U(Xls1$-m<wa z{72*YrMs?+#(XI_f-77J1G+ywtK&zOUAwt;I-wyaTbecm$TSqQcjic` zqrmBC{cL#_U`l7E5w~k4_t!ic>fd64USmwjybzhfsx2_~1?S=ttp8G^bsuG$X=+sW z2-CT24R7Iv@XrZLRC$(HhMciZzjwY_&l2bYTibO+DrW#T^IDJ0m<00{Tf@o}cNc?> z6nM`@cx}Yzpu{rH(?*gtL7NC`xv?hQT?+jPnuf(e==K?F!j)+=R$b! z6%=L-$gX(u`?rF$a$+gtbYp2AsA!yC87sTvWs?<0FR2s}?b!DS14dN}F9clSe4bCu zzMp^FG@H&WBUPxRcVT;cZs;aZGlFp;GMp+@Vj=z70+3@R%z!$@iF#0=?^Df6D=QIe zkRfG1p4Cc-9Z6Z*+!hbm5DlA@*-`c*Kiz#aS5Mv#y_c2!@rhEKM3?s_Z7`?1#ZF{m z=(J1ab56BQ8Ba4j;JflfXgV8dx634wxKZ-EN*BZWoKIj?geOm&bZT4pLBN9$<=v%4C%t{GeGxFORP&btAAS$K@HcYHxQgaKuD86tI$t zX^fk_ByRKIY^134Wx>AF*@+Sh5qyp?#1U1hrewKm-#$Bszt>FZlL4MzfrQSkwNfUF z+xt5^H`fSjq=uY$s20(fK9^)#p&Va&W(msBIR+oi-mK|+`F3M?ph#z~S7Xk1b3vRp z=X0Cr{;g#2dKueu+I8TZ47uw%-ejkgy|?kA?`e*dtP}R^?+y=W`~b8RU_v?MS|lIL>gvqajpSll&#@*a77lfrikT z(9cRonP}nSC9&g~@A8wE$()Oy(WPthTWNN7WTF=+j4g5A=NHgIkPwN02TchA`E1x; zjvFqW?yx29`fde}1=IJLLHrFui|y?~WljvT)-0Pp1$|#^%Vb}+k)y8xdn|qn`{b__ zK+Ek$$sp7CS%}o5)D292LC&tYFl>MPt@?z%Z>}20e-pP48qCC2Xb$FOOJGeGK>WrN z`@KaLjd-2>8kctCN&bCDGrz(#`|mb_QwFSo+71I2Q;>a&;U9F>fv(vR#E1SWu4S3? zZEgD3%<+^yePL98ShQ*0e48IIx8(K10wkXvY2o-IXRN$T0LbVrVaZdck`0(`Isz>! zO)&PJ{%0u}go@+7C#$=7HQ$sUyrrYH)U*t?DV$&Q(}k_;UrE7yjc`r2k$QKr<)g7Z~Z44;@wrClS&w1R`L80)w( zZKO(WKSMMjV??)+|B@b6$)}z@i20|zo~I%Rc`vGEBqhnn{p0WIRXX{w^&bx%KJ2mz z;1p^~=FXzuR_@ozW-GV!eM+*UWy-SL_myS$J?B4%6VKGWTlGJj?}^Q_Y*AXL%3`LA zZkElemoVIEyIXcnC18zM&{ZGu!PgHy3t3PTGz1t!P`A&(K1Z1Y`0!-5E^uUufBfLg zFuGHB!00FK9mAT>NWZnkb*m>&xc&EGEEXpXM@WEgTM7&{#>Ka`px@q`M~tiJ1s<;| zb&IF%DaKIaq-;e{7K9!=T(?MKtXZLx>ZPZvuN%w)ILBEW9JN=HIAO=mQ|8W)r~F|D z(8o5)4sej8+xx{8fKKF>HdPGGX%Eg;w$q^=v7^OB-%vE)Q7V#IFE+C{Av+8WDX@cz z0fV?upJpMM9t+8ATTp65mr7%X`MZ*#c3&-JkI<+acTCzH82Ha>A7{9H=4SSV*r91B zT;AwN$P>cHqJGK%L&|CqWBruNwaTNdIPvNF#(}LcexdzNaT@nu?90Gwzpqx?t@s|j zkmT_|f~n-mb-jLuEPd?xgl3(2Y}Iae4|N)f zMGn-5Mr8;>t*Du3AN+_Km6(Mv%gM9RHvva>Umh`O0fP@|b65m%PL=^BFg6tUKwR9v zW-4I19F7+STN{nz&%|H9hLR=R!*OulF8h=Rb;O?U<~~e6vHW~IcNSXekGL08Q(C_> z6l(F2ZB14%slk*^MIu1J%l)f|AxSm*_4oO`x_fgOE={PPVLvi9XIV!qJl5o6G(c8{ z?ki;^CgJMOF4|4I=Epw_Sf8Lb=e9?S72 zQ2kRwox=(=7~QuFI8Vn(C#}aU2P>dXogC)mw`NkLyzgz6?DEK9Ez@=f7Whf+<0`KvYE|D6{M&BdLAl> zJ$m87|Mc|!9^%mvzI*`97J1Bv-%YXVnW=I0%dN;~x_LwM32{xi#u(PP`UWp8<~+1# zXqKueop83Zy+qD)&hi(HuFRqU>J?TkLA`6rS;(qIA|AI+Jf+@<806=_IKJUq5`kW&oS$7v7io2a~q91kZNo1;sd+pkddYAQuh ztP9(;eWR*?|FDO~wsSnTxQ&eQvkGrL+7eCrDKp~Uh~@sU8;*jFiq4*EAjx(M`3o;m n(+=Gq^@v-q{ez`{lOh5?#9yo@|DE&v&-Du>O~q3AS0Dcuz&bVb literal 0 HcmV?d00001 diff --git a/solutions/19_smart_pointers/arc1.rs b/solutions/19_smart_pointers/arc1.rs index dcf2377a2f..bd76189fee 100644 --- a/solutions/19_smart_pointers/arc1.rs +++ b/solutions/19_smart_pointers/arc1.rs @@ -1,4 +1,45 @@ +// In this exercise, we are given a `Vec` of `u32` called `numbers` with values +// ranging from 0 to 99. We would like to use this set of numbers within 8 +// different threads simultaneously. Each thread is going to get the sum of +// every eighth value with an offset. +// +// The first thread (offset 0), will sum 0, 8, 16, … +// The second thread (offset 1), will sum 1, 9, 17, … +// The third thread (offset 2), will sum 2, 10, 18, … +// … +// The eighth thread (offset 7), will sum 7, 15, 23, … +// +// Each thread should own a reference-counting pointer to the vector of +// numbers. But `Rc` isn't thread-safe. Therefore, we need to use `Arc`. +// +// Don't get distracted by how threads are spawned and joined. We will practice +// that later in the exercises about threads. + +// Don't change the lines below. +#![forbid(unused_imports)] +use std::{sync::Arc, thread}; + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let numbers: Vec<_> = (0..100u32).collect(); + + let shared_numbers = Arc::new(numbers); + // ^^^^^^^^^^^^^^^^^ + + let mut join_handles = Vec::new(); + + for offset in 0..8 { + let child_numbers = Arc::clone(&shared_numbers); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + let handle = thread::spawn(move || { + let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum(); + println!("Sum of offset {offset} is {sum}"); + }); + + join_handles.push(handle); + } + + for handle in join_handles.into_iter() { + handle.join().unwrap(); + } } diff --git a/solutions/19_smart_pointers/box1.rs b/solutions/19_smart_pointers/box1.rs index dcf2377a2f..189cc5623a 100644 --- a/solutions/19_smart_pointers/box1.rs +++ b/solutions/19_smart_pointers/box1.rs @@ -1,4 +1,47 @@ +// At compile time, Rust needs to know how much space a type takes up. This +// becomes problematic for recursive types, where a value can have as part of +// itself another value of the same type. To get around the issue, we can use a +// `Box` - a smart pointer used to store data on the heap, which also allows us +// to wrap a recursive type. +// +// The recursive type we're implementing in this exercise is the "cons list", a +// data structure frequently found in functional programming languages. Each +// item in a cons list contains two elements: The value of the current item and +// the next item. The last item is a value called `Nil`. + +#[derive(PartialEq, Debug)] +enum List { + Cons(i32, Box), + Nil, +} + +fn create_empty_list() -> List { + List::Nil +} + +fn create_non_empty_list() -> List { + List::Cons(42, Box::new(List::Nil)) +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + println!("This is an empty cons list: {:?}", create_empty_list()); + println!( + "This is a non-empty cons list: {:?}", + create_non_empty_list(), + ); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_empty_list() { + assert_eq!(create_empty_list(), List::Nil); + } + + #[test] + fn test_create_non_empty_list() { + assert_ne!(create_empty_list(), create_non_empty_list()); + } } diff --git a/solutions/19_smart_pointers/cow1.rs b/solutions/19_smart_pointers/cow1.rs index dcf2377a2f..461143be86 100644 --- a/solutions/19_smart_pointers/cow1.rs +++ b/solutions/19_smart_pointers/cow1.rs @@ -1,4 +1,69 @@ +// This exercise explores the `Cow` (Clone-On-Write) smart pointer. It can +// enclose and provide immutable access to borrowed data and clone the data +// lazily when mutation or ownership is required. The type is designed to work +// with general borrowed data via the `Borrow` trait. + +use std::borrow::Cow; + +fn abs_all(input: &mut Cow<[i32]>) { + for ind in 0..input.len() { + let value = input[ind]; + if value < 0 { + // Clones into a vector if not already owned. + input.to_mut()[ind] = -value; + } + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reference_mutation() { + // Clone occurs because `input` needs to be mutated. + let vec = vec![-1, 0, 1]; + let mut input = Cow::from(&vec); + abs_all(&mut input); + assert!(matches!(input, Cow::Owned(_))); + } + + #[test] + fn reference_no_mutation() { + // No clone occurs because `input` doesn't need to be mutated. + let vec = vec![0, 1, 2]; + let mut input = Cow::from(&vec); + abs_all(&mut input); + assert!(matches!(input, Cow::Borrowed(_))); + // ^^^^^^^^^^^^^^^^ + } + + #[test] + fn owned_no_mutation() { + // We can also pass `vec` without `&` so `Cow` owns it directly. In this + // case, no mutation occurs (all numbers are already absolute) and thus + // also no clone. But the result is still owned because it was never + // borrowed or mutated. + let vec = vec![0, 1, 2]; + let mut input = Cow::from(vec); + abs_all(&mut input); + assert!(matches!(input, Cow::Owned(_))); + // ^^^^^^^^^^^^^ + } + + #[test] + fn owned_mutation() { + // Of course this is also the case if a mutation does occur (not all + // numbers are absolute). In this case, the call to `to_mut()` in the + // `abs_all` function returns a reference to the same data as before. + let vec = vec![-1, 0, 1]; + let mut input = Cow::from(vec); + abs_all(&mut input); + assert!(matches!(input, Cow::Owned(_))); + // ^^^^^^^^^^^^^ + } } diff --git a/solutions/19_smart_pointers/rc1.rs b/solutions/19_smart_pointers/rc1.rs index dcf2377a2f..c0a41abfa8 100644 --- a/solutions/19_smart_pointers/rc1.rs +++ b/solutions/19_smart_pointers/rc1.rs @@ -1,4 +1,104 @@ +// In this exercise, we want to express the concept of multiple owners via the +// `Rc` type. This is a model of our solar system - there is a `Sun` type and +// multiple `Planet`s. The planets take ownership of the sun, indicating that +// they revolve around the sun. + +use std::rc::Rc; + +#[derive(Debug)] +struct Sun; + +#[derive(Debug)] +enum Planet { + Mercury(Rc), + Venus(Rc), + Earth(Rc), + Mars(Rc), + Jupiter(Rc), + Saturn(Rc), + Uranus(Rc), + Neptune(Rc), +} + +impl Planet { + fn details(&self) { + println!("Hi from {self:?}!"); + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rc1() { + let sun = Rc::new(Sun); + println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference + + let mercury = Planet::Mercury(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references + mercury.details(); + + let venus = Planet::Venus(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references + venus.details(); + + let earth = Planet::Earth(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 4 references + earth.details(); + + let mars = Planet::Mars(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 5 references + mars.details(); + + let jupiter = Planet::Jupiter(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 6 references + jupiter.details(); + + let saturn = Planet::Saturn(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 7 references + saturn.details(); + + // TODO + let uranus = Planet::Uranus(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 8 references + uranus.details(); + + // TODO + let neptune = Planet::Neptune(Rc::clone(&sun)); + println!("reference count = {}", Rc::strong_count(&sun)); // 9 references + neptune.details(); + + assert_eq!(Rc::strong_count(&sun), 9); + + drop(neptune); + println!("reference count = {}", Rc::strong_count(&sun)); // 8 references + + drop(uranus); + println!("reference count = {}", Rc::strong_count(&sun)); // 7 references + + drop(saturn); + println!("reference count = {}", Rc::strong_count(&sun)); // 6 references + + drop(jupiter); + println!("reference count = {}", Rc::strong_count(&sun)); // 5 references + + drop(mars); + println!("reference count = {}", Rc::strong_count(&sun)); // 4 references + + drop(earth); + println!("reference count = {}", Rc::strong_count(&sun)); // 3 references + + drop(venus); + println!("reference count = {}", Rc::strong_count(&sun)); // 2 references + + drop(mercury); + println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference + + assert_eq!(Rc::strong_count(&sun), 1); + } } diff --git a/solutions/20_threads/threads1.rs b/solutions/20_threads/threads1.rs index dcf2377a2f..1fc5bc9c06 100644 --- a/solutions/20_threads/threads1.rs +++ b/solutions/20_threads/threads1.rs @@ -1,4 +1,37 @@ +// This program spawns multiple threads that each runs for at least 250ms, and +// each thread returns how much time it took to complete. The program should +// wait until all the spawned threads have finished and should collect their +// return values into a vector. + +use std::{ + thread, + time::{Duration, Instant}, +}; + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let mut handles = Vec::new(); + for i in 0..10 { + let handle = thread::spawn(move || { + let start = Instant::now(); + thread::sleep(Duration::from_millis(250)); + println!("Thread {i} done"); + start.elapsed().as_millis() + }); + handles.push(handle); + } + + let mut results = Vec::new(); + for handle in handles { + // Collect the results of all threads into the `results` vector. + results.push(handle.join().unwrap()); + } + + if results.len() != 10 { + panic!("Oh no! Some thread isn't done yet!"); + } + + println!(); + for (i, result) in results.into_iter().enumerate() { + println!("Thread {i} took {result}ms"); + } } diff --git a/solutions/20_threads/threads2.rs b/solutions/20_threads/threads2.rs index dcf2377a2f..bc268d63f6 100644 --- a/solutions/20_threads/threads2.rs +++ b/solutions/20_threads/threads2.rs @@ -1,4 +1,41 @@ +// Building on the last exercise, we want all of the threads to complete their +// work. But this time, the spawned threads need to be in charge of updating a +// shared value: `JobStatus.jobs_done` + +use std::{ + sync::{Arc, Mutex}, + thread, + time::Duration, +}; + +struct JobStatus { + jobs_done: u32, +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // `Arc` isn't enough if you want a **mutable** shared state. + // We need to wrap the value with a `Mutex`. + let status = Arc::new(Mutex::new(JobStatus { jobs_done: 0 })); + // ^^^^^^^^^^^ ^ + + let mut handles = Vec::new(); + for _ in 0..10 { + let status_shared = Arc::clone(&status); + let handle = thread::spawn(move || { + thread::sleep(Duration::from_millis(250)); + + // Lock before you update a shared value. + status_shared.lock().unwrap().jobs_done += 1; + // ^^^^^^^^^^^^^^^^ + }); + handles.push(handle); + } + + // Waiting for all jobs to complete. + for handle in handles { + handle.join().unwrap(); + } + + println!("Jobs done: {}", status.lock().unwrap().jobs_done); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } diff --git a/solutions/20_threads/threads3.rs b/solutions/20_threads/threads3.rs index dcf2377a2f..7ceefea016 100644 --- a/solutions/20_threads/threads3.rs +++ b/solutions/20_threads/threads3.rs @@ -1,4 +1,62 @@ +use std::{sync::mpsc, thread, time::Duration}; + +struct Queue { + first_half: Vec, + second_half: Vec, +} + +impl Queue { + fn new() -> Self { + Self { + first_half: vec![1, 2, 3, 4, 5], + second_half: vec![6, 7, 8, 9, 10], + } + } +} + +fn send_tx(q: Queue, tx: mpsc::Sender) { + // Clone the sender `tx` first. + let tx_clone = tx.clone(); + thread::spawn(move || { + for val in q.first_half { + println!("Sending {val:?}"); + // Then use the clone in the first thread. This means that + // `tx_clone` is moved to the first thread and `tx` to the second. + tx_clone.send(val).unwrap(); + thread::sleep(Duration::from_millis(250)); + } + }); + + thread::spawn(move || { + for val in q.second_half { + println!("Sending {val:?}"); + tx.send(val).unwrap(); + thread::sleep(Duration::from_millis(250)); + } + }); +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn threads3() { + let (tx, rx) = mpsc::channel(); + let queue = Queue::new(); + + send_tx(queue, tx); + + let mut received = Vec::with_capacity(10); + for value in rx { + received.push(value); + } + + received.sort(); + assert_eq!(received, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + } } diff --git a/solutions/21_macros/macros1.rs b/solutions/21_macros/macros1.rs index dcf2377a2f..1b861564be 100644 --- a/solutions/21_macros/macros1.rs +++ b/solutions/21_macros/macros1.rs @@ -1,4 +1,10 @@ +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + my_macro!(); + // ^ } diff --git a/solutions/21_macros/macros2.rs b/solutions/21_macros/macros2.rs index dcf2377a2f..b6fd5d2c56 100644 --- a/solutions/21_macros/macros2.rs +++ b/solutions/21_macros/macros2.rs @@ -1,4 +1,10 @@ +// Moved the macro definition to be before its call. +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + my_macro!(); } diff --git a/solutions/21_macros/macros3.rs b/solutions/21_macros/macros3.rs index dcf2377a2f..df35be4d9a 100644 --- a/solutions/21_macros/macros3.rs +++ b/solutions/21_macros/macros3.rs @@ -1,4 +1,13 @@ +// Added the attribute `macro_use` attribute. +#[macro_use] +mod macros { + macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + my_macro!(); } diff --git a/solutions/21_macros/macros4.rs b/solutions/21_macros/macros4.rs index dcf2377a2f..41bcad166a 100644 --- a/solutions/21_macros/macros4.rs +++ b/solutions/21_macros/macros4.rs @@ -1,4 +1,15 @@ +// Added semicolons to separate the macro arms. +#[rustfmt::skip] +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; + ($val:expr) => { + println!("Look at this other macro: {}", $val); + }; +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + my_macro!(); + my_macro!(7777); } diff --git a/solutions/22_clippy/clippy1.rs b/solutions/22_clippy/clippy1.rs index dcf2377a2f..b9d1ec1729 100644 --- a/solutions/22_clippy/clippy1.rs +++ b/solutions/22_clippy/clippy1.rs @@ -1,4 +1,17 @@ +// The Clippy tool is a collection of lints to analyze your code so you can +// catch common mistakes and improve your Rust code. +// +// For these exercises, the code will fail to compile when there are Clippy +// warnings. Check Clippy's suggestions from the output to solve the exercise. + +use std::f32::consts::PI; + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // Use the more accurate `PI` constant. + let pi = PI; + let radius: f32 = 5.0; + + let area = pi * radius.powi(2); + + println!("The area of a circle with radius {radius:.2} is {area:.5}"); } diff --git a/solutions/22_clippy/clippy2.rs b/solutions/22_clippy/clippy2.rs index dcf2377a2f..7f6356285e 100644 --- a/solutions/22_clippy/clippy2.rs +++ b/solutions/22_clippy/clippy2.rs @@ -1,4 +1,10 @@ fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let mut res = 42; + let option = Some(12); + // Use `if-let` instead of iteration. + if let Some(x) = option { + res += x; + } + + println!("{res}"); } diff --git a/solutions/22_clippy/clippy3.rs b/solutions/22_clippy/clippy3.rs index dcf2377a2f..811d184759 100644 --- a/solutions/22_clippy/clippy3.rs +++ b/solutions/22_clippy/clippy3.rs @@ -1,4 +1,31 @@ +use std::mem; + +#[rustfmt::skip] +#[allow(unused_variables, unused_assignments)] fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let my_option: Option<()> = None; + // `unwrap` of an `Option` after checking if it is `None` will panic. + // Use `if-let` instead. + if let Some(value) = my_option { + println!("{value:?}"); + } + + // A comma was missing. + let my_arr = &[ + -1, -2, -3, + -4, -5, -6, + ]; + println!("My array! Here it is: {:?}", my_arr); + + let mut my_empty_vec = vec![1, 2, 3, 4, 5]; + // `resize` mutates a vector instead of returning a new one. + // `resize(0, …)` clears a vector, so it is better to use `clear`. + my_empty_vec.clear(); + println!("This Vec is empty, see? {my_empty_vec:?}"); + + let mut value_a = 45; + let mut value_b = 66; + // Use `mem::swap` to correctly swap two values. + mem::swap(&mut value_a, &mut value_b); + println!("value a: {}; value b: {}", value_a, value_b); } diff --git a/solutions/23_conversions/as_ref_mut.rs b/solutions/23_conversions/as_ref_mut.rs index dcf2377a2f..a5d2d4fa57 100644 --- a/solutions/23_conversions/as_ref_mut.rs +++ b/solutions/23_conversions/as_ref_mut.rs @@ -1,4 +1,60 @@ +// AsRef and AsMut allow for cheap reference-to-reference conversions. Read more +// about them at https://doc.rust-lang.org/std/convert/trait.AsRef.html and +// https://doc.rust-lang.org/std/convert/trait.AsMut.html, respectively. + +// Obtain the number of bytes (not characters) in the given argument +// (`.len()` returns the number of bytes in a string). +fn byte_counter>(arg: T) -> usize { + arg.as_ref().len() +} + +// Obtain the number of characters (not bytes) in the given argument. +fn char_counter>(arg: T) -> usize { + arg.as_ref().chars().count() +} + +// Squares a number using `as_mut()`. +fn num_sq>(arg: &mut T) { + let arg = arg.as_mut(); + *arg *= *arg; +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn different_counts() { + let s = "Café au lait"; + assert_ne!(char_counter(s), byte_counter(s)); + } + + #[test] + fn same_counts() { + let s = "Cafe au lait"; + assert_eq!(char_counter(s), byte_counter(s)); + } + + #[test] + fn different_counts_using_string() { + let s = String::from("Café au lait"); + assert_ne!(char_counter(s.clone()), byte_counter(s)); + } + + #[test] + fn same_counts_using_string() { + let s = String::from("Cafe au lait"); + assert_eq!(char_counter(s.clone()), byte_counter(s)); + } + + #[test] + fn mut_box() { + let mut num: Box = Box::new(3); + num_sq(&mut num); + assert_eq!(*num, 9); + } } diff --git a/solutions/23_conversions/from_into.rs b/solutions/23_conversions/from_into.rs index dcf2377a2f..cec23cb4be 100644 --- a/solutions/23_conversions/from_into.rs +++ b/solutions/23_conversions/from_into.rs @@ -1,4 +1,136 @@ +// The `From` trait is used for value-to-value conversions. If `From` is +// implemented, an implementation of `Into` is automatically provided. +// You can read more about it in the documentation: +// https://doc.rust-lang.org/std/convert/trait.From.html + +#[derive(Debug)] +struct Person { + name: String, + age: u8, +} + +// We implement the Default trait to use it as a fallback when the provided +// string is not convertible into a `Person` object. +impl Default for Person { + fn default() -> Self { + Self { + name: String::from("John"), + age: 30, + } + } +} + +impl From<&str> for Person { + fn from(s: &str) -> Self { + let mut split = s.split(','); + let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else { + // ^^^^ there should be no third element + return Self::default(); + }; + + if name.is_empty() { + return Self::default(); + } + + let Ok(age) = age.parse() else { + return Self::default(); + }; + + Self { + name: name.into(), + age, + } + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // Use the `from` function. + let p1 = Person::from("Mark,20"); + println!("{p1:?}"); + + // Since `From` is implemented for Person, we are able to use `Into`. + let p2: Person = "Gerald,70".into(); + println!("{p2:?}"); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default() { + let dp = Person::default(); + assert_eq!(dp.name, "John"); + assert_eq!(dp.age, 30); + } + + #[test] + fn test_bad_convert() { + let p = Person::from(""); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_good_convert() { + let p = Person::from("Mark,20"); + assert_eq!(p.name, "Mark"); + assert_eq!(p.age, 20); + } + + #[test] + fn test_bad_age() { + let p = Person::from("Mark,twenty"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_comma_and_age() { + let p: Person = Person::from("Mark"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_age() { + let p: Person = Person::from("Mark,"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name() { + let p: Person = Person::from(",1"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name_and_age() { + let p: Person = Person::from(","); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_missing_name_and_invalid_age() { + let p: Person = Person::from(",one"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_trailing_comma() { + let p: Person = Person::from("Mike,32,"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } + + #[test] + fn test_trailing_comma_and_some_string() { + let p: Person = Person::from("Mike,32,dog"); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 30); + } } diff --git a/solutions/23_conversions/from_str.rs b/solutions/23_conversions/from_str.rs index dcf2377a2f..005b50125a 100644 --- a/solutions/23_conversions/from_str.rs +++ b/solutions/23_conversions/from_str.rs @@ -1,4 +1,117 @@ +// This is similar to the previous `from_into` exercise. But this time, we'll +// implement `FromStr` and return errors instead of falling back to a default +// value. Additionally, upon implementing `FromStr`, you can use the `parse` +// method on strings to generate an object of the implementor type. You can read +// more about it in the documentation: +// https://doc.rust-lang.org/std/str/trait.FromStr.html + +use std::num::ParseIntError; +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +struct Person { + name: String, + age: u8, +} + +// We will use this error type for the `FromStr` implementation. +#[derive(Debug, PartialEq)] +enum ParsePersonError { + // Incorrect number of fields + BadLen, + // Empty name field + NoName, + // Wrapped error from parse::() + ParseInt(ParseIntError), +} + +impl FromStr for Person { + type Err = ParsePersonError; + + fn from_str(s: &str) -> Result { + let mut split = s.split(','); + let (Some(name), Some(age), None) = (split.next(), split.next(), split.next()) else { + // ^^^^ there should be no third element + return Err(ParsePersonError::BadLen); + }; + + if name.is_empty() { + return Err(ParsePersonError::NoName); + } + + let age = age.parse().map_err(ParsePersonError::ParseInt)?; + + Ok(Self { + name: name.into(), + age, + }) + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let p = "Mark,20".parse::(); + println!("{p:?}"); +} + +#[cfg(test)] +mod tests { + use super::*; + use ParsePersonError::*; + + #[test] + fn empty_input() { + assert_eq!("".parse::(), Err(BadLen)); + } + + #[test] + fn good_input() { + let p = "John,32".parse::(); + assert!(p.is_ok()); + let p = p.unwrap(); + assert_eq!(p.name, "John"); + assert_eq!(p.age, 32); + } + + #[test] + fn missing_age() { + assert!(matches!("John,".parse::(), Err(ParseInt(_)))); + } + + #[test] + fn invalid_age() { + assert!(matches!("John,twenty".parse::(), Err(ParseInt(_)))); + } + + #[test] + fn missing_comma_and_age() { + assert_eq!("John".parse::(), Err(BadLen)); + } + + #[test] + fn missing_name() { + assert_eq!(",1".parse::(), Err(NoName)); + } + + #[test] + fn missing_name_and_age() { + assert!(matches!(",".parse::(), Err(NoName | ParseInt(_)))); + } + + #[test] + fn missing_name_and_invalid_age() { + assert!(matches!( + ",one".parse::(), + Err(NoName | ParseInt(_)), + )); + } + + #[test] + fn trailing_comma() { + assert_eq!("John,32,".parse::(), Err(BadLen)); + } + + #[test] + fn trailing_comma_and_some_string() { + assert_eq!("John,32,man".parse::(), Err(BadLen)); + } } diff --git a/solutions/23_conversions/try_from_into.rs b/solutions/23_conversions/try_from_into.rs index dcf2377a2f..ee802eb06e 100644 --- a/solutions/23_conversions/try_from_into.rs +++ b/solutions/23_conversions/try_from_into.rs @@ -1,4 +1,193 @@ +// `TryFrom` is a simple and safe type conversion that may fail in a controlled +// way under some circumstances. Basically, this is the same as `From`. The main +// difference is that this should return a `Result` type instead of the target +// type itself. You can read more about it in the documentation: +// https://doc.rust-lang.org/std/convert/trait.TryFrom.html + +#![allow(clippy::useless_vec)] +use std::convert::{TryFrom, TryInto}; + +#[derive(Debug, PartialEq)] +struct Color { + red: u8, + green: u8, + blue: u8, +} + +// We will use this error type for the `TryFrom` conversions. +#[derive(Debug, PartialEq)] +enum IntoColorError { + // Incorrect length of slice + BadLen, + // Integer conversion error + IntConversion, +} + +impl TryFrom<(i16, i16, i16)> for Color { + type Error = IntoColorError; + + fn try_from(tuple: (i16, i16, i16)) -> Result { + let (Ok(red), Ok(green), Ok(blue)) = ( + u8::try_from(tuple.0), + u8::try_from(tuple.1), + u8::try_from(tuple.2), + ) else { + return Err(IntoColorError::IntConversion); + }; + + Ok(Self { red, green, blue }) + } +} + +impl TryFrom<[i16; 3]> for Color { + type Error = IntoColorError; + + fn try_from(arr: [i16; 3]) -> Result { + // Reuse the implementation for a tuple. + Self::try_from((arr[0], arr[1], arr[2])) + } +} + +impl TryFrom<&[i16]> for Color { + type Error = IntoColorError; + + fn try_from(slice: &[i16]) -> Result { + // Check the length. + if slice.len() != 3 { + return Err(IntoColorError::BadLen); + } + + // Reuse the implementation for a tuple. + Self::try_from((slice[0], slice[1], slice[2])) + } +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // Using the `try_from` function. + let c1 = Color::try_from((183, 65, 14)); + println!("{c1:?}"); + + // Since `TryFrom` is implemented for `Color`, we can use `TryInto`. + let c2: Result = [183, 65, 14].try_into(); + println!("{c2:?}"); + + let v = vec![183, 65, 14]; + // With slice we should use the `try_from` function + let c3 = Color::try_from(&v[..]); + println!("{c3:?}"); + // or put the slice within round brackets and use `try_into`. + let c4: Result = (&v[..]).try_into(); + println!("{c4:?}"); +} + +#[cfg(test)] +mod tests { + use super::*; + use IntoColorError::*; + + #[test] + fn test_tuple_out_of_range_positive() { + assert_eq!(Color::try_from((256, 1000, 10000)), Err(IntConversion)); + } + + #[test] + fn test_tuple_out_of_range_negative() { + assert_eq!(Color::try_from((-1, -10, -256)), Err(IntConversion)); + } + + #[test] + fn test_tuple_sum() { + assert_eq!(Color::try_from((-1, 255, 255)), Err(IntConversion)); + } + + #[test] + fn test_tuple_correct() { + let c: Result = (183, 65, 14).try_into(); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14, + } + ); + } + + #[test] + fn test_array_out_of_range_positive() { + let c: Result = [1000, 10000, 256].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_out_of_range_negative() { + let c: Result = [-10, -256, -1].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_sum() { + let c: Result = [-1, 255, 255].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_correct() { + let c: Result = [183, 65, 14].try_into(); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14 + } + ); + } + + #[test] + fn test_slice_out_of_range_positive() { + let arr = [10000, 256, 1000]; + assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); + } + + #[test] + fn test_slice_out_of_range_negative() { + let arr = [-256, -1, -10]; + assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); + } + + #[test] + fn test_slice_sum() { + let arr = [-1, 255, 255]; + assert_eq!(Color::try_from(&arr[..]), Err(IntConversion)); + } + + #[test] + fn test_slice_correct() { + let v = vec![183, 65, 14]; + let c: Result = Color::try_from(&v[..]); + assert!(c.is_ok()); + assert_eq!( + c.unwrap(), + Color { + red: 183, + green: 65, + blue: 14, + } + ); + } + + #[test] + fn test_slice_excess_length() { + let v = vec![0, 0, 0, 0]; + assert_eq!(Color::try_from(&v[..]), Err(BadLen)); + } + + #[test] + fn test_slice_insufficient_length() { + let v = vec![0, 0]; + assert_eq!(Color::try_from(&v[..]), Err(BadLen)); + } } diff --git a/solutions/23_conversions/using_as.rs b/solutions/23_conversions/using_as.rs index dcf2377a2f..14b92ebf05 100644 --- a/solutions/23_conversions/using_as.rs +++ b/solutions/23_conversions/using_as.rs @@ -1,4 +1,24 @@ +// Type casting in Rust is done via the usage of the `as` operator. +// Note that the `as` operator is not only used when type casting. It also helps +// with renaming imports. + +fn average(values: &[f64]) -> f64 { + let total = values.iter().sum::(); + total / values.len() as f64 + // ^^^^^^ +} + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + let values = [3.5, 0.3, 13.0, 11.7]; + println!("{}", average(&values)); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn returns_proper_type_and_value() { + assert_eq!(average(&[3.5, 0.3, 13.0, 11.7]), 7.125); + } } From 2e5a22ccd27b2f3097554f325a02fe42c6e36721 Mon Sep 17 00:00:00 2001 From: BraCR10 Date: Sun, 17 Aug 2025 22:45:26 -0600 Subject: [PATCH 5/6] fix --- README.md | 2 ++ exercises/README.md | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d227af2935..273e29ecce 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # rustlings + +![alt text](image.png) \ No newline at end of file diff --git a/exercises/README.md b/exercises/README.md index 3dc99dc37d..237f2f1edc 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -1,4 +1,3 @@ -![alt text](image.png) # Exercise to Book Chapter mapping | Exercise | Book Chapter | From 385fb53d07073b978b73fa4325fd61aef07a8273 Mon Sep 17 00:00:00 2001 From: BraCR10 Date: Sun, 17 Aug 2025 22:45:53 -0600 Subject: [PATCH 6/6] img --- exercises/image.png => image.png | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename exercises/image.png => image.png (100%) diff --git a/exercises/image.png b/image.png similarity index 100% rename from exercises/image.png rename to image.png