From 0666f77c425ac2c0e5dec30590c4b516fb848c4d Mon Sep 17 00:00:00 2001 From: FReenen Date: Tue, 29 Oct 2024 11:28:19 +0100 Subject: [PATCH] some more progress --- .rustlings-state.txt | 36 ++++++- exercises/05_vecs/vecs1.rs | 1 + exercises/05_vecs/vecs2.rs | 4 +- .../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 | 8 +- exercises/07_structs/structs1.rs | 8 +- exercises/07_structs/structs2.rs | 5 + exercises/07_structs/structs3.rs | 15 ++- exercises/08_enums/enums1.rs | 5 + exercises/08_enums/enums2.rs | 5 + exercises/08_enums/enums3.rs | 14 +++ exercises/09_strings/strings1.rs | 2 +- exercises/09_strings/strings2.rs | 2 +- exercises/09_strings/strings3.rs | 3 + exercises/09_strings/strings4.rs | 20 ++-- exercises/10_modules/modules1.rs | 2 +- exercises/10_modules/modules2.rs | 4 +- exercises/10_modules/modules3.rs | 2 +- exercises/11_hashmaps/hashmaps1.rs | 6 +- exercises/11_hashmaps/hashmaps2.rs | 1 + exercises/11_hashmaps/hashmaps3.rs | 28 +++++- exercises/12_options/options1.rs | 8 +- exercises/12_options/options2.rs | 10 +- exercises/12_options/options3.rs | 2 +- exercises/13_error_handling/errors1.rs | 6 +- exercises/13_error_handling/errors2.rs | 6 +- exercises/13_error_handling/errors3.rs | 7 +- exercises/13_error_handling/errors4.rs | 6 +- exercises/13_error_handling/errors5.rs | 2 +- exercises/13_error_handling/errors6.rs | 4 +- exercises/quizzes/quiz2.rs | 16 ++- solutions/05_vecs/vecs1.rs | 25 ++++- solutions/05_vecs/vecs2.rs | 57 ++++++++++- .../06_move_semantics/move_semantics1.rs | 27 ++++- .../06_move_semantics/move_semantics2.rs | 30 +++++- .../06_move_semantics/move_semantics3.rs | 24 ++++- .../06_move_semantics/move_semantics4.rs | 21 +++- .../06_move_semantics/move_semantics5.rs | 25 ++++- solutions/07_structs/structs1.rs | 51 +++++++++- solutions/07_structs/structs2.rs | 53 +++++++++- solutions/07_structs/structs3.rs | 85 +++++++++++++++- solutions/08_enums/enums1.rs | 18 +++- solutions/08_enums/enums2.rs | 41 +++++++- solutions/08_enums/enums3.rs | 94 +++++++++++++++++- solutions/09_strings/strings1.rs | 11 ++- solutions/09_strings/strings2.rs | 17 +++- solutions/09_strings/strings3.rs | 50 +++++++++- solutions/09_strings/strings4.rs | 40 +++++++- solutions/10_modules/modules1.rs | 17 +++- solutions/10_modules/modules2.rs | 26 ++++- solutions/10_modules/modules3.rs | 8 +- solutions/11_hashmaps/hashmaps1.rs | 44 ++++++++- solutions/11_hashmaps/hashmaps2.rs | 98 ++++++++++++++++++- solutions/11_hashmaps/hashmaps3.rs | 89 ++++++++++++++++- solutions/12_options/options1.rs | 41 +++++++- solutions/12_options/options2.rs | 37 ++++++- solutions/12_options/options3.rs | 28 +++++- solutions/13_error_handling/errors1.rs | 39 +++++++- solutions/13_error_handling/errors2.rs | 60 +++++++++++- solutions/13_error_handling/errors3.rs | 34 ++++++- solutions/13_error_handling/errors4.rs | 44 ++++++++- solutions/13_error_handling/errors5.rs | 56 ++++++++++- solutions/quizzes/quiz2.rs | 92 ++++++++++++++++- 66 files changed, 1484 insertions(+), 144 deletions(-) diff --git a/.rustlings-state.txt b/.rustlings-state.txt index d7a2678..d10cd67 100644 --- a/.rustlings-state.txt +++ b/.rustlings-state.txt @@ -1,6 +1,6 @@ DON'T EDIT THIS FILE! -vecs1 +errors6 intro1 intro2 @@ -24,4 +24,36 @@ primitive_types2 primitive_types3 primitive_types4 primitive_types5 -primitive_types6 \ No newline at end of file +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 \ No newline at end of file diff --git a/exercises/05_vecs/vecs1.rs b/exercises/05_vecs/vecs1.rs index 68e1aff..8a0156d 100644 --- a/exercises/05_vecs/vecs1.rs +++ b/exercises/05_vecs/vecs1.rs @@ -4,6 +4,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 = vec![a[0], a[1], a[2], a[3]]; (a, v) } diff --git a/exercises/05_vecs/vecs2.rs b/exercises/05_vecs/vecs2.rs index a9be258..623277a 100644 --- a/exercises/05_vecs/vecs2.rs +++ b/exercises/05_vecs/vecs2.rs @@ -4,6 +4,8 @@ fn vec_loop(input: &[i32]) -> Vec { 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 +26,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 4eb3d61..bf55943 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 a3ab7a0..e205b81 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 11dbbbe..4a90c21 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 56da988..89b412f 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 cd0dafd..78d8c77 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,7 @@ 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 959c4c6..e02d82d 100644 --- a/exercises/07_structs/structs1.rs +++ b/exercises/07_structs/structs1.rs @@ -1,9 +1,12 @@ 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(/* TODO: Add the fields that the test `tuple_structs` expects */u8, u8, u8); #[derive(Debug)] struct UnitStruct; @@ -20,6 +23,7 @@ mod tests { 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); @@ -30,6 +34,7 @@ mod tests { 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); @@ -40,6 +45,7 @@ mod tests { 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 79141af..13550c7 100644 --- a/exercises/07_structs/structs2.rs +++ b/exercises/07_structs/structs2.rs @@ -35,6 +35,11 @@ mod tests { // TODO: Create your own order using the update syntax and template above! // let your_order = + let your_order = Order { + name: String::from("Hacker in Rust"), + count: 1, + ..order_template + }; 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 69e5ced..07058aa 100644 --- a/exercises/07_structs/structs3.rs +++ b/exercises/07_structs/structs3.rs @@ -24,19 +24,30 @@ 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.sender_country != self.recipient_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 } } fn main() { // You can optionally experiment here. + let ding = Package::new(String::from("NL"), String::from("DK"), 10); + if ding.is_international() + { + println!("is international"); + } + else + { + println!("is not international"); + } } #[cfg(test)] diff --git a/exercises/08_enums/enums1.rs b/exercises/08_enums/enums1.rs index c0d0c30..1aa6af3 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 29ed1b6..eb9b60f 100644 --- a/exercises/08_enums/enums2.rs +++ b/exercises/08_enums/enums2.rs @@ -9,6 +9,11 @@ struct Point { #[derive(Debug)] enum Message { // TODO: Define the different variants used below. + Resize { width: u16, height: u16 }, + 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 66c4675..9f65011 100644 --- a/exercises/08_enums/enums3.rs +++ b/exercises/08_enums/enums3.rs @@ -1,3 +1,5 @@ +use std::str::Matches; + struct Point { x: u64, y: u64, @@ -5,6 +7,11 @@ struct Point { enum Message { // TODO: Implement the message variant types based on their usage below. + Resize { width: u64, height: u64 }, + Move(Point), + Echo(String), + ChangeColor(u8, u8, u8), + Quit } struct State { @@ -42,6 +49,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 } => self.resize(width, height), + Message::Move(point) => self.move_position(point), + Message::Echo(msg) => self.echo(msg), + Message::ChangeColor(r, g, b) => self.change_color(r, g, b), + Message::Quit => self.quit(), + } } } diff --git a/exercises/09_strings/strings1.rs b/exercises/09_strings/strings1.rs index 6abdbb4..06755aa 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 93d9cb6..2ed89c5 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) { 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 39fce18..6324c4c 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_owned() + " world!" } fn replace_me(input: &str) -> String { // TODO: Replace "cars" in the string with "balloons". + input.replace("cars", "balloons") } fn main() { diff --git a/exercises/09_strings/strings4.rs b/exercises/09_strings/strings4.rs index 4730726..3f7c5e7 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("nice weather".into()); - 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 d97ab23..81981df 100644 --- a/exercises/10_modules/modules1.rs +++ b/exercises/10_modules/modules1.rs @@ -5,7 +5,7 @@ mod sausage_factory { String::from("Ginger") } - fn make_sausage() { + pub fn make_sausage() { get_secret_recipe(); println!("sausage!"); } diff --git a/exercises/10_modules/modules2.rs b/exercises/10_modules/modules2.rs index 02eb80a..56e1402 100644 --- a/exercises/10_modules/modules2.rs +++ b/exercises/10_modules/modules2.rs @@ -4,8 +4,8 @@ #[allow(dead_code)] 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"; diff --git a/exercises/10_modules/modules3.rs b/exercises/10_modules/modules3.rs index 691608d..403b7ea 100644 --- a/exercises/10_modules/modules3.rs +++ b/exercises/10_modules/modules3.rs @@ -3,7 +3,7 @@ // TODO: Bring `SystemTime` and `UNIX_EPOCH` from the `std::time` module into // 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) { diff --git a/exercises/11_hashmaps/hashmaps1.rs b/exercises/11_hashmaps/hashmaps1.rs index 74001d0..32e76d6 100644 --- a/exercises/11_hashmaps/hashmaps1.rs +++ b/exercises/11_hashmaps/hashmaps1.rs @@ -8,12 +8,16 @@ 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); // TODO: Put more fruits in your basket. + basket.insert(String::from("apple"), 0); + basket.insert(String::from("ananas"), 3); + basket.insert(String::from("anananas"), 1); + basket.insert(String::from("pineapple"), 2); basket } diff --git a/exercises/11_hashmaps/hashmaps2.rs b/exercises/11_hashmaps/hashmaps2.rs index e9f53fe..c71525f 100644 --- a/exercises/11_hashmaps/hashmaps2.rs +++ b/exercises/11_hashmaps/hashmaps2.rs @@ -32,6 +32,7 @@ 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! + basket.entry(fruit).or_insert(1); } } diff --git a/exercises/11_hashmaps/hashmaps3.rs b/exercises/11_hashmaps/hashmaps3.rs index 7e9584d..e9f593d 100644 --- a/exercises/11_hashmaps/hashmaps3.rs +++ b/exercises/11_hashmaps/hashmaps3.rs @@ -17,7 +17,7 @@ struct TeamScores { fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> { // The name of the team is the key and its associated struct is the value. - let mut scores = HashMap::new(); + let mut scores: HashMap<&str, TeamScores> = HashMap::new(); for line in results.lines() { let mut split_iterator = line.split(','); @@ -31,6 +31,32 @@ 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. + let team = scores.get_mut(team_1_name); + match team { + Some(t) => { + t.goals_scored += team_1_score; + t.goals_conceded += team_2_score; + }, + None => { + scores.insert(team_1_name, TeamScores { + goals_scored: team_1_score, + goals_conceded: team_2_score + }); + } + } + let team = scores.get_mut(team_2_name); + match team { + Some(t) => { + t.goals_scored += team_2_score; + t.goals_conceded += team_1_score; + }, + None => { + scores.insert(team_2_name, TeamScores { + goals_scored: team_2_score, + goals_conceded: team_1_score + }); + } + } } scores diff --git a/exercises/12_options/options1.rs b/exercises/12_options/options1.rs index 9964807..7b8f6c6 100644 --- a/exercises/12_options/options1.rs +++ b/exercises/12_options/options1.rs @@ -4,6 +4,12 @@ // `hour_of_day` is higher than 23. fn maybe_icecream(hour_of_day: u16) -> Option { // TODO: Complete the function body. + match hour_of_day { + hour if hour<22 => Some(5), + hour if hour <24 => Some(0), + _ => None + + } } fn main() { @@ -18,7 +24,7 @@ mod tests { fn raw_value() { // TODO: Fix this test. How do you get the value contained in the // Option? - let icecreams = maybe_icecream(12); + let icecreams = maybe_icecream(12).unwrap(); assert_eq!(icecreams, 5); // Don't change this line. } diff --git a/exercises/12_options/options2.rs b/exercises/12_options/options2.rs index 07c27c6..5bed6a1 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,9 +29,11 @@ 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() { - assert_eq!(integer, cursor); - cursor -= 1; + while let Some(integer_opt) = optional_integers.pop() { + if let Some(integer) = integer_opt { + assert_eq!(integer, cursor); + cursor -= 1; + } } assert_eq!(cursor, 0); diff --git a/exercises/12_options/options3.rs b/exercises/12_options/options3.rs index 4cedb51..495b383 100644 --- a/exercises/12_options/options3.rs +++ b/exercises/12_options/options3.rs @@ -9,7 +9,7 @@ fn main() { // TODO: Fix the compiler error by adding something to this match statement. match optional_point { - Some(p) => println!("Co-ordinates are {},{}", p.x, p.y), + Some(ref p) => println!("Co-ordinates are {},{}", p.x, p.y), _ => panic!("No match!"), } diff --git a/exercises/13_error_handling/errors1.rs b/exercises/13_error_handling/errors1.rs index e07fddc..0cced95 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("Empty names aren't allowed".to_string()) } 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 defe359..6ee7642 100644 --- a/exercises/13_error_handling/errors2.rs +++ b/exercises/13_error_handling/errors2.rs @@ -22,8 +22,10 @@ fn total_cost(item_quantity: &str) -> Result { // TODO: Handle the error case as described above. let qty = item_quantity.parse::(); - - Ok(qty * cost_per_item + processing_fee) + match qty { + Ok(x) => Ok(x * cost_per_item + processing_fee), + Err(x) => Err(x) + } } fn main() { diff --git a/exercises/13_error_handling/errors3.rs b/exercises/13_error_handling/errors3.rs index 8e8c38a..711eb24 100644 --- a/exercises/13_error_handling/errors3.rs +++ b/exercises/13_error_handling/errors3.rs @@ -2,7 +2,7 @@ // `total_cost` function from the previous exercise. It's not working though! // Why not? What should we do to fix it? -use std::num::ParseIntError; +use std::{io::Empty, num::ParseIntError}; // Don't change this function. fn total_cost(item_quantity: &str) -> Result { @@ -15,12 +15,12 @@ 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"; // Don't change this line. - let cost = total_cost(pretend_user_input)?; + let cost: i32 = total_cost(pretend_user_input)?; if cost > tokens { println!("You can't afford that many!"); @@ -28,4 +28,5 @@ 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 ba01e54..4f4fbf7 100644 --- a/exercises/13_error_handling/errors4.rs +++ b/exercises/13_error_handling/errors4.rs @@ -10,7 +10,11 @@ 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), + _ => Ok(Self(value as u64)) + } } } diff --git a/exercises/13_error_handling/errors5.rs b/exercises/13_error_handling/errors5.rs index 5721835..a8e1f8b 100644 --- a/exercises/13_error_handling/errors5.rs +++ b/exercises/13_error_handling/errors5.rs @@ -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 b1995e0..b9b3371 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)] diff --git a/exercises/quizzes/quiz2.rs b/exercises/quizzes/quiz2.rs index 2cddba9..012b648 100644 --- a/exercises/quizzes/quiz2.rs +++ b/exercises/quizzes/quiz2.rs @@ -24,10 +24,22 @@ enum Command { } mod my_module { + use std::array; + use super::Command; // TODO: Complete the function as described above. - // pub fn transformer(input: ???) -> ??? { ??? } + pub fn transformer(input: Vec<(String, Command)>) -> Vec { + let mut output = Vec::new(); + for transform in input { + match transform.1 { + Command::Trim => output.push(transform.0.trim().to_string()), + Command::Uppercase => output.push(transform.0.to_uppercase()), + Command::Append(n) => output.push(transform.0 + &"bar".repeat(n)) + } + } + output + } } fn main() { @@ -37,7 +49,7 @@ fn main() { #[cfg(test)] mod tests { // TODO: What do we need to import to have `transformer` in scope? - // use ???; + use crate::my_module::transformer; use super::Command; #[test] diff --git a/solutions/05_vecs/vecs1.rs b/solutions/05_vecs/vecs1.rs index dcf2377..55b5676 100644 --- a/solutions/05_vecs/vecs1.rs +++ b/solutions/05_vecs/vecs1.rs @@ -1,4 +1,23 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn array_and_vec() -> ([i32; 4], Vec) { + let a = [10, 20, 30, 40]; // Array + + // Used the `vec!` macro. + let v = vec![10, 20, 30, 40]; + + (a, v) +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_array_and_vec_similarity() { + let (a, v) = array_and_vec(); + assert_eq!(a, *v); + } } diff --git a/solutions/05_vecs/vecs2.rs b/solutions/05_vecs/vecs2.rs index dcf2377..87f7625 100644 --- a/solutions/05_vecs/vecs2.rs +++ b/solutions/05_vecs/vecs2.rs @@ -1,4 +1,55 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn vec_loop(input: &[i32]) -> Vec { + let mut output = Vec::new(); + + for element in input { + output.push(2 * element); + } + + output +} + +fn vec_map_example(input: &[i32]) -> Vec { + // An example of collecting a vector after mapping. + // We map each element of the `input` slice to its value plus 1. + // If the input is `[1, 2, 3]`, the output is `[2, 3, 4]`. + input.iter().map(|element| element + 1).collect() +} + +fn vec_map(input: &[i32]) -> Vec { + // We will dive deeper into iterators, but for now, this is all what you + // had to do! + // Advanced note: This method is more efficient because it automatically + // preallocates enough capacity. This can be done manually in `vec_loop` + // using `Vec::with_capacity(input.len())` instead of `Vec::new()`. + input.iter().map(|element| 2 * element).collect() +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vec_loop() { + let input = [2, 4, 6, 8, 10]; + let ans = vec_loop(&input); + assert_eq!(ans, [4, 8, 12, 16, 20]); + } + + #[test] + fn test_vec_map_example() { + let input = [1, 2, 3]; + let ans = vec_map_example(&input); + assert_eq!(ans, [2, 3, 4]); + } + + #[test] + fn test_vec_map() { + let input = [2, 4, 6, 8, 10]; + let ans = vec_map(&input); + assert_eq!(ans, [4, 8, 12, 16, 20]); + } } diff --git a/solutions/06_move_semantics/move_semantics1.rs b/solutions/06_move_semantics/move_semantics1.rs index dcf2377..ac34e7a 100644 --- a/solutions/06_move_semantics/move_semantics1.rs +++ b/solutions/06_move_semantics/move_semantics1.rs @@ -1,4 +1,25 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn fill_vec(vec: Vec) -> Vec { + let mut vec = vec; + // ^^^ added + + vec.push(88); + + vec +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn move_semantics1() { + let vec0 = vec![22, 44, 66]; + let vec1 = fill_vec(vec0); + // `vec0` can't be accessed anymore because it is moved to `fill_vec`. + assert_eq!(vec1, vec![22, 44, 66, 88]); + } } diff --git a/solutions/06_move_semantics/move_semantics2.rs b/solutions/06_move_semantics/move_semantics2.rs index dcf2377..7bcd33a 100644 --- a/solutions/06_move_semantics/move_semantics2.rs +++ b/solutions/06_move_semantics/move_semantics2.rs @@ -1,4 +1,28 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn fill_vec(vec: Vec) -> Vec { + let mut vec = vec; + + vec.push(88); + + vec +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn move_semantics2() { + let vec0 = vec![22, 44, 66]; + + // Cloning `vec0` so that the clone is moved into `fill_vec`, not `vec0` + // itself. + let vec1 = fill_vec(vec0.clone()); + + assert_eq!(vec0, [22, 44, 66]); + assert_eq!(vec1, [22, 44, 66, 88]); + } } diff --git a/solutions/06_move_semantics/move_semantics3.rs b/solutions/06_move_semantics/move_semantics3.rs index dcf2377..7ba4006 100644 --- a/solutions/06_move_semantics/move_semantics3.rs +++ b/solutions/06_move_semantics/move_semantics3.rs @@ -1,4 +1,22 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn fill_vec(mut vec: Vec) -> Vec { + // ^^^ added + vec.push(88); + + vec +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn move_semantics3() { + let vec0 = vec![22, 44, 66]; + let vec1 = fill_vec(vec0); + assert_eq!(vec1, [22, 44, 66, 88]); + } } diff --git a/solutions/06_move_semantics/move_semantics4.rs b/solutions/06_move_semantics/move_semantics4.rs index dcf2377..64fdd9d 100644 --- a/solutions/06_move_semantics/move_semantics4.rs +++ b/solutions/06_move_semantics/move_semantics4.rs @@ -1,4 +1,21 @@ 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 { + // 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(); + let y = &mut x; + // `y` used here. + y.push(42); + // The mutable reference `y` is not used anymore, + // therefore a new reference can be created. + let z = &mut x; + z.push(13); + assert_eq!(x, [42, 13]); + } } diff --git a/solutions/06_move_semantics/move_semantics5.rs b/solutions/06_move_semantics/move_semantics5.rs index dcf2377..1410e91 100644 --- a/solutions/06_move_semantics/move_semantics5.rs +++ b/solutions/06_move_semantics/move_semantics5.rs @@ -1,4 +1,23 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +#![allow(clippy::ptr_arg)] + +// Borrows instead of taking ownership. +// It is recommended to use `&str` instead of `&String` here. But this is +// enough for now because we didn't handle strings yet. +fn get_char(data: &String) -> char { + data.chars().last().unwrap() +} + +// Takes ownership instead of borrowing. +fn string_uppercase(mut data: String) { + data = data.to_uppercase(); + + println!("{data}"); +} + +fn main() { + let data = "Rust is great!".to_string(); + + get_char(&data); + + string_uppercase(data); } diff --git a/solutions/07_structs/structs1.rs b/solutions/07_structs/structs1.rs index dcf2377..98fafcc 100644 --- a/solutions/07_structs/structs1.rs +++ b/solutions/07_structs/structs1.rs @@ -1,4 +1,49 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +struct ColorRegularStruct { + red: u8, + green: u8, + blue: u8, +} + +struct ColorTupleStruct(u8, u8, u8); + +#[derive(Debug)] +struct UnitStruct; + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn regular_structs() { + let green = ColorRegularStruct { + red: 0, + green: 255, + blue: 0, + }; + + assert_eq!(green.red, 0); + assert_eq!(green.green, 255); + assert_eq!(green.blue, 0); + } + + #[test] + fn tuple_structs() { + let green = ColorTupleStruct(0, 255, 0); + + assert_eq!(green.0, 0); + assert_eq!(green.1, 255); + assert_eq!(green.2, 0); + } + + #[test] + fn unit_structs() { + let unit_struct = UnitStruct; + let message = format!("{unit_struct:?}s are fun!"); + + assert_eq!(message, "UnitStructs are fun!"); + } } diff --git a/solutions/07_structs/structs2.rs b/solutions/07_structs/structs2.rs index dcf2377..589dd93 100644 --- a/solutions/07_structs/structs2.rs +++ b/solutions/07_structs/structs2.rs @@ -1,4 +1,51 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +#[derive(Debug)] +struct Order { + name: String, + year: u32, + made_by_phone: bool, + made_by_mobile: bool, + made_by_email: bool, + item_number: u32, + count: u32, +} + +fn create_order_template() -> Order { + Order { + name: String::from("Bob"), + year: 2019, + made_by_phone: false, + made_by_mobile: false, + made_by_email: true, + item_number: 123, + count: 0, + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn your_order() { + let order_template = create_order_template(); + + let your_order = Order { + name: String::from("Hacker in Rust"), + count: 1, + // Struct update syntax + ..order_template + }; + + assert_eq!(your_order.name, "Hacker in Rust"); + assert_eq!(your_order.year, order_template.year); + assert_eq!(your_order.made_by_phone, order_template.made_by_phone); + assert_eq!(your_order.made_by_mobile, order_template.made_by_mobile); + assert_eq!(your_order.made_by_email, order_template.made_by_email); + assert_eq!(your_order.item_number, order_template.item_number); + assert_eq!(your_order.count, 1); + } } diff --git a/solutions/07_structs/structs3.rs b/solutions/07_structs/structs3.rs index dcf2377..3f878cc 100644 --- a/solutions/07_structs/structs3.rs +++ b/solutions/07_structs/structs3.rs @@ -1,4 +1,83 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +#[derive(Debug)] +struct Package { + sender_country: String, + recipient_country: String, + weight_in_grams: u32, +} + +impl Package { + fn new(sender_country: String, recipient_country: String, weight_in_grams: u32) -> Self { + if weight_in_grams < 10 { + // This isn't how you should handle errors in Rust, but we will + // learn about error handling later. + panic!("Can't ship a package with weight below 10 grams"); + } + + Self { + sender_country, + recipient_country, + weight_in_grams, + } + } + + fn is_international(&self) -> bool { + // ^^^^^^^ added + self.sender_country != self.recipient_country + } + + fn get_fees(&self, cents_per_gram: u32) -> u32 { + // ^^^^^^ added + self.weight_in_grams * cents_per_gram + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn fail_creating_weightless_package() { + let sender_country = String::from("Spain"); + let recipient_country = String::from("Austria"); + + Package::new(sender_country, recipient_country, 5); + } + + #[test] + fn create_international_package() { + let sender_country = String::from("Spain"); + let recipient_country = String::from("Russia"); + + let package = Package::new(sender_country, recipient_country, 1200); + + assert!(package.is_international()); + } + + #[test] + fn create_local_package() { + let sender_country = String::from("Canada"); + let recipient_country = sender_country.clone(); + + let package = Package::new(sender_country, recipient_country, 1200); + + assert!(!package.is_international()); + } + + #[test] + fn calculate_transport_fees() { + let sender_country = String::from("Spain"); + let recipient_country = String::from("Spain"); + + let cents_per_gram = 3; + + let package = Package::new(sender_country, recipient_country, 1500); + + assert_eq!(package.get_fees(cents_per_gram), 4500); + assert_eq!(package.get_fees(cents_per_gram * 2), 9000); + } } diff --git a/solutions/08_enums/enums1.rs b/solutions/08_enums/enums1.rs index dcf2377..97a5cc0 100644 --- a/solutions/08_enums/enums1.rs +++ b/solutions/08_enums/enums1.rs @@ -1,4 +1,16 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +#[derive(Debug)] +enum Message { + Resize, + Move, + Echo, + ChangeColor, + Quit, +} + +fn main() { + println!("{:?}", Message::Resize); + println!("{:?}", Message::Move); + println!("{:?}", Message::Echo); + println!("{:?}", Message::ChangeColor); + println!("{:?}", Message::Quit); } diff --git a/solutions/08_enums/enums2.rs b/solutions/08_enums/enums2.rs index dcf2377..2ee0553 100644 --- a/solutions/08_enums/enums2.rs +++ b/solutions/08_enums/enums2.rs @@ -1,4 +1,39 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +#![allow(dead_code)] + +#[derive(Debug)] +struct Point { + x: u64, + y: u64, +} + +#[derive(Debug)] +enum Message { + Resize { width: u64, height: u64 }, + Move(Point), + Echo(String), + ChangeColor(u8, u8, u8), + Quit, +} + +impl Message { + fn call(&self) { + println!("{self:?}"); + } +} + +fn main() { + let messages = [ + Message::Resize { + width: 10, + height: 30, + }, + Message::Move(Point { x: 10, y: 15 }), + Message::Echo(String::from("hello world")), + Message::ChangeColor(200, 255, 255), + Message::Quit, + ]; + + for message in &messages { + message.call(); + } } diff --git a/solutions/08_enums/enums3.rs b/solutions/08_enums/enums3.rs index dcf2377..4bc26b7 100644 --- a/solutions/08_enums/enums3.rs +++ b/solutions/08_enums/enums3.rs @@ -1,4 +1,92 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +struct Point { + x: u64, + y: u64, +} + +enum Message { + Resize { width: u64, height: u64 }, + Move(Point), + Echo(String), + ChangeColor(u8, u8, u8), + Quit, +} + +struct State { + width: u64, + height: u64, + position: Point, + message: String, + color: (u8, u8, u8), + quit: bool, +} + +impl State { + fn resize(&mut self, width: u64, height: u64) { + self.width = width; + self.height = height; + } + + fn move_position(&mut self, point: Point) { + self.position = point; + } + + fn echo(&mut self, s: String) { + self.message = s; + } + + fn change_color(&mut self, red: u8, green: u8, blue: u8) { + self.color = (red, green, blue); + } + + fn quit(&mut self) { + self.quit = true; + } + + fn process(&mut self, message: Message) { + match message { + Message::Resize { width, height } => self.resize(width, height), + Message::Move(point) => self.move_position(point), + Message::Echo(s) => self.echo(s), + Message::ChangeColor(r, g, b) => self.change_color(r, g, b), + Message::Quit => self.quit(), + } + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_match_message_call() { + let mut state = State { + width: 0, + height: 0, + position: Point { x: 0, y: 0 }, + message: String::from("hello world"), + color: (0, 0, 0), + quit: false, + }; + + state.process(Message::Resize { + width: 10, + height: 30, + }); + state.process(Message::Move(Point { x: 10, y: 15 })); + state.process(Message::Echo(String::from("Hello world!"))); + state.process(Message::ChangeColor(255, 0, 255)); + state.process(Message::Quit); + + assert_eq!(state.width, 10); + assert_eq!(state.height, 30); + assert_eq!(state.position.x, 10); + assert_eq!(state.position.y, 15); + assert_eq!(state.message, "Hello world!"); + assert_eq!(state.color, (255, 0, 255)); + assert!(state.quit); + } } diff --git a/solutions/09_strings/strings1.rs b/solutions/09_strings/strings1.rs index dcf2377..f7ba811 100644 --- a/solutions/09_strings/strings1.rs +++ b/solutions/09_strings/strings1.rs @@ -1,4 +1,9 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn current_favorite_color() -> String { + // Equivalent to `String::from("blue")` + "blue".to_string() +} + +fn main() { + let answer = current_favorite_color(); + println!("My current favorite color is {answer}"); } diff --git a/solutions/09_strings/strings2.rs b/solutions/09_strings/strings2.rs index dcf2377..7de311f 100644 --- a/solutions/09_strings/strings2.rs +++ b/solutions/09_strings/strings2.rs @@ -1,4 +1,15 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn is_a_color_word(attempt: &str) -> bool { + attempt == "green" || attempt == "blue" || attempt == "red" +} + +fn main() { + let word = String::from("green"); + + if is_a_color_word(&word) { + // ^ added to have `&String` which is automatically + // coerced to `&str` by the compiler. + println!("That is a color word I know!"); + } else { + println!("That is not a color word I know."); + } } diff --git a/solutions/09_strings/strings3.rs b/solutions/09_strings/strings3.rs index dcf2377..a478e62 100644 --- a/solutions/09_strings/strings3.rs +++ b/solutions/09_strings/strings3.rs @@ -1,4 +1,48 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn trim_me(input: &str) -> &str { + input.trim() +} + +fn compose_me(input: &str) -> String { + // The macro `format!` has the same syntax as `println!`, but it returns a + // string instead of printing it to the terminal. + // Equivalent to `input.to_string() + " world!"` + format!("{input} world!") +} + +fn replace_me(input: &str) -> String { + input.replace("cars", "balloons") +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn trim_a_string() { + assert_eq!(trim_me("Hello! "), "Hello!"); + assert_eq!(trim_me(" What's up!"), "What's up!"); + assert_eq!(trim_me(" Hola! "), "Hola!"); + } + + #[test] + fn compose_a_string() { + assert_eq!(compose_me("Hello"), "Hello world!"); + assert_eq!(compose_me("Goodbye"), "Goodbye world!"); + } + + #[test] + fn replace_a_string() { + assert_eq!( + replace_me("I think cars are cool"), + "I think balloons are cool", + ); + assert_eq!( + replace_me("I love to look at cars"), + "I love to look at balloons", + ); + } } diff --git a/solutions/09_strings/strings4.rs b/solutions/09_strings/strings4.rs index dcf2377..3c69b97 100644 --- a/solutions/09_strings/strings4.rs +++ b/solutions/09_strings/strings4.rs @@ -1,4 +1,38 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn string_slice(arg: &str) { + println!("{arg}"); +} + +fn string(arg: String) { + println!("{arg}"); +} + +fn main() { + string_slice("blue"); + + string("red".to_string()); + + string(String::from("hi")); + + string("rust is fun!".to_owned()); + + // Here, both answers work. + // `.into()` converts a type into an expected type. + // If it is called where `String` is expected, it will convert `&str` to `String`. + string("nice weather".into()); + // But if it is called where `&str` is expected, then `&str` is kept `&str` since no conversion is needed. + // If you remove the `#[allow(…)]` line, then Clippy will tell you to remove `.into()` below since it is a useless conversion. + #[allow(clippy::useless_conversion)] + string_slice("nice weather".into()); + + string(format!("Interpolation {}", "Station")); + + // WARNING: This is byte indexing, not character indexing. + // Character indexing can be done using `s.chars().nth(INDEX)`. + string_slice(&String::from("abc")[0..1]); + + string_slice(" hello there ".trim()); + + string("Happy Monday!".replace("Mon", "Tues")); + + string("mY sHiFt KeY iS sTiCkY".to_lowercase()); } diff --git a/solutions/10_modules/modules1.rs b/solutions/10_modules/modules1.rs index dcf2377..873b412 100644 --- a/solutions/10_modules/modules1.rs +++ b/solutions/10_modules/modules1.rs @@ -1,4 +1,15 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +mod sausage_factory { + fn get_secret_recipe() -> String { + String::from("Ginger") + } + + // Added `pub` before `fn` to make the function accessible outside the module. + pub fn make_sausage() { + get_secret_recipe(); + println!("sausage!"); + } +} + +fn main() { + sausage_factory::make_sausage(); } diff --git a/solutions/10_modules/modules2.rs b/solutions/10_modules/modules2.rs index dcf2377..298d76e 100644 --- a/solutions/10_modules/modules2.rs +++ b/solutions/10_modules/modules2.rs @@ -1,4 +1,24 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +#[allow(dead_code)] +mod delicious_snacks { + // Added `pub` and used the expected alias after `as`. + pub use self::fruits::PEAR as fruit; + pub use self::veggies::CUCUMBER as veggie; + + mod fruits { + pub const PEAR: &str = "Pear"; + pub const APPLE: &str = "Apple"; + } + + mod veggies { + pub const CUCUMBER: &str = "Cucumber"; + pub const CARROT: &str = "Carrot"; + } +} + +fn main() { + println!( + "favorite snacks: {} and {}", + delicious_snacks::fruit, + delicious_snacks::veggie, + ); } diff --git a/solutions/10_modules/modules3.rs b/solutions/10_modules/modules3.rs index dcf2377..99ff5a7 100644 --- a/solutions/10_modules/modules3.rs +++ b/solutions/10_modules/modules3.rs @@ -1,4 +1,8 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + 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/solutions/11_hashmaps/hashmaps1.rs b/solutions/11_hashmaps/hashmaps1.rs index dcf2377..3a787c4 100644 --- a/solutions/11_hashmaps/hashmaps1.rs +++ b/solutions/11_hashmaps/hashmaps1.rs @@ -1,4 +1,42 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// A basket of fruits in the form of a hash map needs to be defined. The key +// represents the name of the fruit and the value represents how many of that +// particular fruit is in the basket. You have to put at least 3 different +// types of fruits (e.g apple, banana, mango) in the basket and the total count +// of all the fruits should be at least 5. + +use std::collections::HashMap; + +fn fruit_basket() -> HashMap { + // Declare the hash map. + let mut basket = HashMap::new(); + + // Two bananas are already given for you :) + basket.insert(String::from("banana"), 2); + + // Put more fruits in your basket. + basket.insert(String::from("apple"), 3); + basket.insert(String::from("mango"), 1); + + basket +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn at_least_three_types_of_fruits() { + let basket = fruit_basket(); + assert!(basket.len() >= 3); + } + + #[test] + fn at_least_five_fruits() { + let basket = fruit_basket(); + assert!(basket.values().sum::() >= 5); + } } diff --git a/solutions/11_hashmaps/hashmaps2.rs b/solutions/11_hashmaps/hashmaps2.rs index dcf2377..75e6ec2 100644 --- a/solutions/11_hashmaps/hashmaps2.rs +++ b/solutions/11_hashmaps/hashmaps2.rs @@ -1,4 +1,96 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// We're collecting different fruits to bake a delicious fruit cake. For this, +// we have a basket, which we'll represent in the form of a hash map. The key +// represents the name of each fruit we collect and the value represents how +// many of that particular fruit we have collected. Three types of fruits - +// Apple (4), Mango (2) and Lychee (5) are already in the basket hash map. You +// must add fruit to the basket so that there is at least one of each kind and +// more than 11 in total - we have a lot of mouths to feed. You are not allowed +// to insert any more of the fruits that are already in the basket (Apple, +// Mango, and Lychee). + +use std::collections::HashMap; + +#[derive(Hash, PartialEq, Eq, Debug)] +enum Fruit { + Apple, + Banana, + Mango, + Lychee, + Pineapple, +} + +fn fruit_basket(basket: &mut HashMap) { + let fruit_kinds = [ + Fruit::Apple, + Fruit::Banana, + Fruit::Mango, + Fruit::Lychee, + Fruit::Pineapple, + ]; + + for fruit in fruit_kinds { + // If fruit doesn't exist, insert it with some value. + basket.entry(fruit).or_insert(5); + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + // Don't modify this function! + fn get_fruit_basket() -> HashMap { + let content = [(Fruit::Apple, 4), (Fruit::Mango, 2), (Fruit::Lychee, 5)]; + HashMap::from_iter(content) + } + + #[test] + fn test_given_fruits_are_not_modified() { + let mut basket = get_fruit_basket(); + fruit_basket(&mut basket); + assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4); + assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2); + assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5); + } + + #[test] + fn at_least_five_types_of_fruits() { + let mut basket = get_fruit_basket(); + fruit_basket(&mut basket); + let count_fruit_kinds = basket.len(); + assert!(count_fruit_kinds >= 5); + } + + #[test] + fn greater_than_eleven_fruits() { + let mut basket = get_fruit_basket(); + fruit_basket(&mut basket); + let count = basket.values().sum::(); + assert!(count > 11); + } + + #[test] + fn all_fruit_types_in_basket() { + let fruit_kinds = [ + Fruit::Apple, + Fruit::Banana, + Fruit::Mango, + Fruit::Lychee, + Fruit::Pineapple, + ]; + + let mut basket = get_fruit_basket(); + fruit_basket(&mut basket); + + for fruit_kind in fruit_kinds { + let Some(amount) = basket.get(&fruit_kind) else { + panic!("Fruit kind {fruit_kind:?} was not found in basket"); + }; + assert!(*amount > 0); + } + } } diff --git a/solutions/11_hashmaps/hashmaps3.rs b/solutions/11_hashmaps/hashmaps3.rs index dcf2377..8a5d30b 100644 --- a/solutions/11_hashmaps/hashmaps3.rs +++ b/solutions/11_hashmaps/hashmaps3.rs @@ -1,4 +1,87 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// A list of scores (one per line) of a soccer match is given. Each line is of +// the form ",,," +// Example: "England,France,4,2" (England scored 4 goals, France 2). +// +// You have to build a scores table containing the name of the team, the total +// number of goals the team scored, and the total number of goals the team +// conceded. + +use std::collections::HashMap; + +// A structure to store the goal details of a team. +#[derive(Default)] +struct TeamScores { + goals_scored: u8, + goals_conceded: u8, +} + +fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> { + // The name of the team is the key and its associated struct is the value. + let mut scores = HashMap::new(); + + for line in results.lines() { + let mut split_iterator = line.split(','); + // NOTE: We use `unwrap` because we didn't deal with error handling yet. + let team_1_name = split_iterator.next().unwrap(); + let team_2_name = split_iterator.next().unwrap(); + let team_1_score: u8 = split_iterator.next().unwrap().parse().unwrap(); + let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap(); + + // Insert the default with zeros if a team doesn't exist yet. + let team_1 = scores + .entry(team_1_name) + .or_insert_with(TeamScores::default); + // Update the values. + team_1.goals_scored += team_1_score; + team_1.goals_conceded += team_2_score; + + // Similarly for the second team. + let team_2 = scores + .entry(team_2_name) + .or_insert_with(TeamScores::default); + team_2.goals_scored += team_2_score; + team_2.goals_conceded += team_1_score; + } + + scores +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + const RESULTS: &str = "England,France,4,2 +France,Italy,3,1 +Poland,Spain,2,0 +Germany,England,2,1 +England,Spain,1,0"; + + #[test] + 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))); + } + + #[test] + fn validate_team_score_1() { + let scores = build_scores_table(RESULTS); + let team = scores.get("England").unwrap(); + assert_eq!(team.goals_scored, 6); + assert_eq!(team.goals_conceded, 4); + } + + #[test] + fn validate_team_score_2() { + let scores = build_scores_table(RESULTS); + let team = scores.get("Spain").unwrap(); + assert_eq!(team.goals_scored, 0); + assert_eq!(team.goals_conceded, 3); + } } diff --git a/solutions/12_options/options1.rs b/solutions/12_options/options1.rs index dcf2377..4d615dd 100644 --- a/solutions/12_options/options1.rs +++ b/solutions/12_options/options1.rs @@ -1,4 +1,39 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// 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 icecream is left (value 0). Return `None` if +// `hour_of_day` is higher than 23. +fn maybe_icecream(hour_of_day: u16) -> Option { + match hour_of_day { + 0..=21 => Some(5), + 22..=23 => Some(0), + _ => None, + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn raw_value() { + // Using `unwrap` is fine in a test. + let icecreams = maybe_icecream(12).unwrap(); + + assert_eq!(icecreams, 5); + } + + #[test] + 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/solutions/12_options/options2.rs b/solutions/12_options/options2.rs index dcf2377..0f24665 100644 --- a/solutions/12_options/options2.rs +++ b/solutions/12_options/options2.rs @@ -1,4 +1,37 @@ 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 simple_option() { + let target = "rustlings"; + let optional_target = Some(target); + + // if-let + if let Some(word) = optional_target { + assert_eq!(word, target); + } + } + + #[test] + fn layered_option() { + let range = 10; + let mut optional_integers: Vec> = vec![None]; + + for i in 1..=range { + optional_integers.push(Some(i)); + } + + let mut cursor = range; + + // while-let with nested pattern matching + while let Some(Some(integer)) = optional_integers.pop() { + assert_eq!(integer, cursor); + cursor -= 1; + } + + assert_eq!(cursor, 0); + } } diff --git a/solutions/12_options/options3.rs b/solutions/12_options/options3.rs index dcf2377..0081eeb 100644 --- a/solutions/12_options/options3.rs +++ b/solutions/12_options/options3.rs @@ -1,4 +1,26 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +#[derive(Debug)] +struct Point { + x: i32, + y: i32, +} + +fn main() { + let optional_point = Some(Point { x: 100, y: 200 }); + + // Solution 1: Matching over the `Option` (not `&Option`) but without moving + // out of the `Some` variant. + match optional_point { + Some(ref p) => println!("Co-ordinates are {},{}", p.x, p.y), + // ^^^ added + _ => panic!("No match!"), + } + + // Solution 2: Matching over a reference (`&Option`) by added `&` before + // `optional_point`. + match &optional_point { + Some(p) => println!("Co-ordinates are {},{}", p.x, p.y), + _ => panic!("No match!"), + } + + println!("{optional_point:?}"); } diff --git a/solutions/13_error_handling/errors1.rs b/solutions/13_error_handling/errors1.rs index dcf2377..f552ca7 100644 --- a/solutions/13_error_handling/errors1.rs +++ b/solutions/13_error_handling/errors1.rs @@ -1,4 +1,37 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn generate_nametag_text(name: String) -> Result { + // ^^^^^^ ^^^^^^ + if name.is_empty() { + // `Err(String)` instead of `None`. + Err("Empty names aren't allowed".to_string()) + } else { + // `Ok` instead of `Some`. + Ok(format!("Hi! My name is {name}")) + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generates_nametag_text_for_a_nonempty_name() { + assert_eq!( + generate_nametag_text("Beyoncé".to_string()).as_deref(), + Ok("Hi! My name is Beyoncé"), + ); + } + + #[test] + fn explains_why_generating_nametag_text_fails() { + assert_eq!( + generate_nametag_text(String::new()) + .as_ref() + .map_err(|e| e.as_str()), + Err("Empty names aren't allowed"), + ); + } } diff --git a/solutions/13_error_handling/errors2.rs b/solutions/13_error_handling/errors2.rs index dcf2377..0597c8c 100644 --- a/solutions/13_error_handling/errors2.rs +++ b/solutions/13_error_handling/errors2.rs @@ -1,4 +1,58 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Say we're writing a game where you can buy items with tokens. All items cost +// 5 tokens, and whenever you purchase items there is a processing fee of 1 +// token. A player of the game will type in how many items they want to buy, and +// the `total_cost` function will calculate the total cost of the items. Since +// the player typed in the quantity, we get it as a string. They might have +// typed anything, not just numbers! +// +// Right now, this function isn't handling the error case at all. What we want +// to do is: If we call the `total_cost` function on a string that is not a +// number, that function will return a `ParseIntError`. In that case, we want to +// immediately return that error from our function and not try to multiply and +// add. +// +// There are at least two ways to implement this that are both correct. But one +// is a lot shorter! + +use std::num::ParseIntError; + +#[allow(unused_variables)] +fn total_cost(item_quantity: &str) -> Result { + let processing_fee = 1; + let cost_per_item = 5; + + // Added `?` to propagate the error. + let qty = item_quantity.parse::()?; + // ^ added + + // Equivalent to this verbose version: + let qty = match item_quantity.parse::() { + Ok(v) => v, + Err(e) => return Err(e), + }; + + Ok(qty * cost_per_item + processing_fee) +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + use std::num::IntErrorKind; + + #[test] + fn item_quantity_is_a_valid_number() { + assert_eq!(total_cost("34"), Ok(171)); + } + + #[test] + fn item_quantity_is_an_invalid_number() { + assert_eq!( + total_cost("beep boop").unwrap_err().kind(), + &IntErrorKind::InvalidDigit, + ); + } } diff --git a/solutions/13_error_handling/errors3.rs b/solutions/13_error_handling/errors3.rs index dcf2377..63f4aba 100644 --- a/solutions/13_error_handling/errors3.rs +++ b/solutions/13_error_handling/errors3.rs @@ -1,4 +1,32 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// This is a program that is trying to use a completed version of the +// `total_cost` function from the previous exercise. It's not working though! +// Why not? What should we do to fix it? + +use std::num::ParseIntError; + +// Don't change this function. +fn total_cost(item_quantity: &str) -> Result { + let processing_fee = 1; + let cost_per_item = 5; + let qty = item_quantity.parse::()?; + + Ok(qty * cost_per_item + processing_fee) +} + +fn main() -> Result<(), ParseIntError> { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ added + let mut tokens = 100; + let pretend_user_input = "8"; + + let cost = total_cost(pretend_user_input)?; + + if cost > tokens { + println!("You can't afford that many!"); + } else { + tokens -= cost; + println!("You now have {tokens} tokens."); + } + + // Added this line to return the `Ok` variant of the expected `Result`. + Ok(()) } diff --git a/solutions/13_error_handling/errors4.rs b/solutions/13_error_handling/errors4.rs index dcf2377..70c5f1c 100644 --- a/solutions/13_error_handling/errors4.rs +++ b/solutions/13_error_handling/errors4.rs @@ -1,4 +1,42 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +use std::cmp::Ordering; + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + match value.cmp(&0) { + Ordering::Less => Err(CreationError::Negative), + Ordering::Equal => Err(CreationError::Zero), + Ordering::Greater => Ok(Self(value as u64)), + } + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_creation() { + assert_eq!( + PositiveNonzeroInteger::new(10), + Ok(PositiveNonzeroInteger(10)), + ); + assert_eq!( + PositiveNonzeroInteger::new(-10), + Err(CreationError::Negative), + ); + assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero)); + } } diff --git a/solutions/13_error_handling/errors5.rs b/solutions/13_error_handling/errors5.rs index dcf2377..c1424ee 100644 --- a/solutions/13_error_handling/errors5.rs +++ b/solutions/13_error_handling/errors5.rs @@ -1,4 +1,54 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// This exercise is an altered version of the `errors4` exercise. It uses some +// concepts that we won't get to until later in the course, like `Box` and the +// `From` trait. It's not important to understand them in detail right now, but +// you can read ahead if you like. For now, think of the `Box` type as +// an "I want anything that does ???" type. +// +// 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` 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`. + +use std::error::Error; +use std::fmt; + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +// This is required so that `CreationError` can implement `Error`. +impl fmt::Display for CreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let description = match *self { + CreationError::Negative => "number is negative", + CreationError::Zero => "number is zero", + }; + f.write_str(description) + } +} + +impl Error for CreationError {} + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + match value { + x if x < 0 => Err(CreationError::Negative), + 0 => Err(CreationError::Zero), + x => Ok(PositiveNonzeroInteger(x as u64)), + } + } +} + +fn main() -> Result<(), Box> { + let pretend_user_input = "42"; + let x: i64 = pretend_user_input.parse()?; + println!("output={:?}", PositiveNonzeroInteger::new(x)?); + Ok(()) } diff --git a/solutions/quizzes/quiz2.rs b/solutions/quizzes/quiz2.rs index dcf2377..58cbe4e 100644 --- a/solutions/quizzes/quiz2.rs +++ b/solutions/quizzes/quiz2.rs @@ -1,4 +1,90 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Let's build a little machine in the form of a function. As input, we're going +// to give a list of strings and commands. These commands determine what action +// is going to be applied to the string. It can either be: +// - Uppercase the string +// - Trim the string +// - Append "bar" to the string a specified amount of times +// +// The exact form of this will be: +// - The input is going to be a vector of 2-length tuples, +// the first element is the string, the second one is the command. +// - The output element is going to be a vector of strings. + +enum Command { + Uppercase, + Trim, + Append(usize), +} + +mod my_module { + use super::Command; + + // The solution with a loop. Check out `transformer_iter` for a version + // with iterators. + pub fn transformer(input: Vec<(String, Command)>) -> Vec { + let mut output = Vec::new(); + + for (string, command) in input { + // Create the new string. + let new_string = match command { + Command::Uppercase => string.to_uppercase(), + Command::Trim => string.trim().to_string(), + Command::Append(n) => string + &"bar".repeat(n), + }; + + // Push the new string to the output vector. + output.push(new_string); + } + + output + } + + // Equivalent to `transform` but uses an iterator instead of a loop for + // comparison. Don't worry, we will practice iterators later ;) + pub fn transformer_iter(input: Vec<(String, Command)>) -> Vec { + input + .into_iter() + .map(|(string, command)| match command { + Command::Uppercase => string.to_uppercase(), + Command::Trim => string.trim().to_string(), + Command::Append(n) => string + &"bar".repeat(n), + }) + .collect() + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + // Import `transformer`. + use super::my_module::transformer; + + use super::my_module::transformer_iter; + use super::Command; + + #[test] + fn it_works() { + for transformer in [transformer, transformer_iter] { + let input = vec![ + ("hello".to_string(), Command::Uppercase), + (" all roads lead to rome! ".to_string(), Command::Trim), + ("foo".to_string(), Command::Append(1)), + ("bar".to_string(), Command::Append(5)), + ]; + let output = transformer(input); + + assert_eq!( + output, + [ + "HELLO", + "all roads lead to rome!", + "foobar", + "barbarbarbarbarbar", + ] + ); + } + } }