diff options
| author | mo8it <mo8it@proton.me> | 2024-07-05 13:39:50 +0200 |
|---|---|---|
| committer | mo8it <mo8it@proton.me> | 2024-07-05 13:39:50 +0200 |
| commit | 7123c7ae3a9605fbe962e4ef0a0f1424cd16fef8 (patch) | |
| tree | c67f7e62bb9a179ae4fdbab492501cb6847e64c7 /solutions | |
| parent | 77b687d501771c24bd83294d97b8e6f9ffa92d6b (diff) | |
| parent | 4d9c346a173bb722b929f3ea3c00f84954483e24 (diff) | |
Merge remote-tracking branch 'upstream/main' into fix-enum-variant-inconsistency
Diffstat (limited to 'solutions')
94 files changed, 3670 insertions, 0 deletions
diff --git a/solutions/00_intro/intro1.rs b/solutions/00_intro/intro1.rs new file mode 100644 index 0000000..4fe8454 --- /dev/null +++ b/solutions/00_intro/intro1.rs @@ -0,0 +1,5 @@ +fn main() { + // Congratulations, you finished the first exercise 🎉 + // As an introduction to Rustlings, the first exercise only required + // entering `n` in the terminal to go to the next exercise. +} diff --git a/solutions/00_intro/intro2.rs b/solutions/00_intro/intro2.rs new file mode 100644 index 0000000..b8e031a --- /dev/null +++ b/solutions/00_intro/intro2.rs @@ -0,0 +1,4 @@ +fn main() { + // `println!` instead of `printline!`. + println!("Hello world!"); +} diff --git a/solutions/01_variables/variables1.rs b/solutions/01_variables/variables1.rs new file mode 100644 index 0000000..58d046b --- /dev/null +++ b/solutions/01_variables/variables1.rs @@ -0,0 +1,6 @@ +fn main() { + // Declaring variables requires the `let` keyword. + let x = 5; + + println!("x has the value {x}"); +} diff --git a/solutions/01_variables/variables2.rs b/solutions/01_variables/variables2.rs new file mode 100644 index 0000000..50b8d1b --- /dev/null +++ b/solutions/01_variables/variables2.rs @@ -0,0 +1,16 @@ +fn main() { + // The easiest way to fix the compiler error is to initialize the + // variable `x`. By setting its value to an integer, Rust infers its type + // as `i32` which is the default type for integers. + let x = 42; + + // But we can enforce a type different from the default `i32` by adding + // a type annotation: + // let x: u8 = 42; + + if x == 10 { + println!("x is ten!"); + } else { + println!("x is not ten!"); + } +} diff --git a/solutions/01_variables/variables3.rs b/solutions/01_variables/variables3.rs new file mode 100644 index 0000000..15f6557 --- /dev/null +++ b/solutions/01_variables/variables3.rs @@ -0,0 +1,15 @@ +#![allow(clippy::needless_late_init)] + +fn main() { + // Reading uninitialized variables isn't allowed in Rust! + // Therefore, we need to assign a value first. + let x: i32 = 42; + + println!("Number {x}"); + + // It is possible to declare a variable and initialize it later. + // But it can't be used before initialization. + let y: i32; + y = 42; + println!("Number {y}"); +} diff --git a/solutions/01_variables/variables4.rs b/solutions/01_variables/variables4.rs new file mode 100644 index 0000000..7de6bcb --- /dev/null +++ b/solutions/01_variables/variables4.rs @@ -0,0 +1,9 @@ +fn main() { + // In Rust, variables are immutable by default. + // Adding the `mut` keyword after `let` makes the declared variable mutable. + let mut x = 3; + println!("Number {x}"); + + x = 5; + println!("Number {x}"); +} diff --git a/solutions/01_variables/variables5.rs b/solutions/01_variables/variables5.rs new file mode 100644 index 0000000..9057754 --- /dev/null +++ b/solutions/01_variables/variables5.rs @@ -0,0 +1,9 @@ +fn main() { + let number = "T-H-R-E-E"; + println!("Spell a number: {}", number); + + // Using variable shadowing + // https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing + let number = 3; + println!("Number plus two is: {}", number + 2); +} diff --git a/solutions/01_variables/variables6.rs b/solutions/01_variables/variables6.rs new file mode 100644 index 0000000..25b7a1e --- /dev/null +++ b/solutions/01_variables/variables6.rs @@ -0,0 +1,6 @@ +// The type of constants must always be annotated. +const NUMBER: u64 = 3; + +fn main() { + println!("Number: {NUMBER}"); +} diff --git a/solutions/02_functions/functions1.rs b/solutions/02_functions/functions1.rs new file mode 100644 index 0000000..dc52744 --- /dev/null +++ b/solutions/02_functions/functions1.rs @@ -0,0 +1,8 @@ +// Some function with the name `call_me` without arguments or a return value. +fn call_me() { + println!("Hello world!"); +} + +fn main() { + call_me(); +} diff --git a/solutions/02_functions/functions2.rs b/solutions/02_functions/functions2.rs new file mode 100644 index 0000000..f14ffa3 --- /dev/null +++ b/solutions/02_functions/functions2.rs @@ -0,0 +1,11 @@ +// The type of function arguments must be annotated. +// Added the type annotation `u64`. +fn call_me(num: u64) { + for i in 0..num { + println!("Ring! Call number {}", i + 1); + } +} + +fn main() { + call_me(3); +} diff --git a/solutions/02_functions/functions3.rs b/solutions/02_functions/functions3.rs new file mode 100644 index 0000000..c581c42 --- /dev/null +++ b/solutions/02_functions/functions3.rs @@ -0,0 +1,10 @@ +fn call_me(num: u32) { + for i in 0..num { + println!("Ring! Call number {}", i + 1); + } +} + +fn main() { + // `call_me` expects an argument. + call_me(5); +} diff --git a/solutions/02_functions/functions4.rs b/solutions/02_functions/functions4.rs new file mode 100644 index 0000000..f823de2 --- /dev/null +++ b/solutions/02_functions/functions4.rs @@ -0,0 +1,17 @@ +fn is_even(num: i64) -> bool { + num % 2 == 0 +} + +// The return type must always be annotated. +fn sale_price(price: i64) -> i64 { + if is_even(price) { + price - 10 + } else { + price - 3 + } +} + +fn main() { + let original_price = 51; + println!("Your sale price is {}", sale_price(original_price)); +} diff --git a/solutions/02_functions/functions5.rs b/solutions/02_functions/functions5.rs new file mode 100644 index 0000000..677f327 --- /dev/null +++ b/solutions/02_functions/functions5.rs @@ -0,0 +1,9 @@ +fn square(num: i32) -> i32 { + // Removed the semicolon `;` at the end of the line below to implicitly return the result. + num * num +} + +fn main() { + let answer = square(3); + println!("The square of 3 is {answer}"); +} diff --git a/solutions/03_if/if1.rs b/solutions/03_if/if1.rs new file mode 100644 index 0000000..079c671 --- /dev/null +++ b/solutions/03_if/if1.rs @@ -0,0 +1,32 @@ +fn bigger(a: i32, b: i32) -> i32 { + if a > b { + a + } else { + b + } +} + +fn main() { + // You can optionally experiment here. +} + +// Don't mind this for now :) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ten_is_bigger_than_eight() { + assert_eq!(10, bigger(10, 8)); + } + + #[test] + fn fortytwo_is_bigger_than_thirtytwo() { + assert_eq!(42, bigger(32, 42)); + } + + #[test] + fn equal_numbers() { + assert_eq!(42, bigger(42, 42)); + } +} diff --git a/solutions/03_if/if2.rs b/solutions/03_if/if2.rs new file mode 100644 index 0000000..440bba0 --- /dev/null +++ b/solutions/03_if/if2.rs @@ -0,0 +1,33 @@ +fn foo_if_fizz(fizzish: &str) -> &str { + if fizzish == "fizz" { + "foo" + } else if fizzish == "fuzz" { + "bar" + } else { + "baz" + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn foo_for_fizz() { + assert_eq!(foo_if_fizz("fizz"), "foo"); + } + + #[test] + fn bar_for_fuzz() { + assert_eq!(foo_if_fizz("fuzz"), "bar"); + } + + #[test] + fn default_to_baz() { + assert_eq!(foo_if_fizz("literally anything"), "baz"); + } +} diff --git a/solutions/03_if/if3.rs b/solutions/03_if/if3.rs new file mode 100644 index 0000000..571644d --- /dev/null +++ b/solutions/03_if/if3.rs @@ -0,0 +1,53 @@ +fn animal_habitat(animal: &str) -> &str { + let identifier = if animal == "crab" { + 1 + } else if animal == "gopher" { + 2 + } else if animal == "snake" { + 3 + } else { + // Any unused identifier. + 4 + }; + + // Instead of such an identifier, you would use an enum in Rust. + // But we didn't get into enums yet. + if identifier == 1 { + "Beach" + } else if identifier == 2 { + "Burrow" + } else if identifier == 3 { + "Desert" + } else { + "Unknown" + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gopher_lives_in_burrow() { + assert_eq!(animal_habitat("gopher"), "Burrow") + } + + #[test] + fn snake_lives_in_desert() { + assert_eq!(animal_habitat("snake"), "Desert") + } + + #[test] + fn crab_lives_on_beach() { + assert_eq!(animal_habitat("crab"), "Beach") + } + + #[test] + fn unknown_animal() { + assert_eq!(animal_habitat("dinosaur"), "Unknown") + } +} diff --git a/solutions/04_primitive_types/primitive_types1.rs b/solutions/04_primitive_types/primitive_types1.rs new file mode 100644 index 0000000..fac6ec0 --- /dev/null +++ b/solutions/04_primitive_types/primitive_types1.rs @@ -0,0 +1,11 @@ +fn main() { + let is_morning = true; + if is_morning { + println!("Good morning!"); + } + + let is_evening = !is_morning; + if is_evening { + println!("Good evening!"); + } +} diff --git a/solutions/04_primitive_types/primitive_types2.rs b/solutions/04_primitive_types/primitive_types2.rs new file mode 100644 index 0000000..eecc680 --- /dev/null +++ b/solutions/04_primitive_types/primitive_types2.rs @@ -0,0 +1,21 @@ +fn main() { + let my_first_initial = 'C'; + if my_first_initial.is_alphabetic() { + println!("Alphabetical!"); + } else if my_first_initial.is_numeric() { + println!("Numerical!"); + } else { + println!("Neither alphabetic nor numeric!"); + } + + // Example with an emoji. + let your_character = '🦀'; + + if your_character.is_alphabetic() { + println!("Alphabetical!"); + } else if your_character.is_numeric() { + println!("Numerical!"); + } else { + println!("Neither alphabetic nor numeric!"); + } +} diff --git a/solutions/04_primitive_types/primitive_types3.rs b/solutions/04_primitive_types/primitive_types3.rs new file mode 100644 index 0000000..8dd109f --- /dev/null +++ b/solutions/04_primitive_types/primitive_types3.rs @@ -0,0 +1,11 @@ +fn main() { + // An array with 100 elements of the value 42. + let a = [42; 100]; + + if a.len() >= 100 { + println!("Wow, that's a big array!"); + } else { + println!("Meh, I eat arrays like that for breakfast."); + panic!("Array not big enough, more elements needed"); + } +} diff --git a/solutions/04_primitive_types/primitive_types4.rs b/solutions/04_primitive_types/primitive_types4.rs new file mode 100644 index 0000000..4807e66 --- /dev/null +++ b/solutions/04_primitive_types/primitive_types4.rs @@ -0,0 +1,23 @@ +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + #[test] + fn slice_out_of_array() { + let a = [1, 2, 3, 4, 5]; + // 0 1 2 3 4 <- indices + // ------- + // | + // +--- slice + + // Note that the upper index 4 is excluded. + let nice_slice = &a[1..4]; + assert_eq!([2, 3, 4], nice_slice); + + // The upper index can be included by using the syntax `..=` (with `=` sign) + let nice_slice = &a[1..=3]; + assert_eq!([2, 3, 4], nice_slice); + } +} diff --git a/solutions/04_primitive_types/primitive_types5.rs b/solutions/04_primitive_types/primitive_types5.rs new file mode 100644 index 0000000..46d7ae8 --- /dev/null +++ b/solutions/04_primitive_types/primitive_types5.rs @@ -0,0 +1,8 @@ +fn main() { + let cat = ("Furry McFurson", 3.5); + + // Destructuring the tuple. + let (name, age) = cat; + + println!("{name} is {age} years old"); +} diff --git a/solutions/04_primitive_types/primitive_types6.rs b/solutions/04_primitive_types/primitive_types6.rs new file mode 100644 index 0000000..9b7c277 --- /dev/null +++ b/solutions/04_primitive_types/primitive_types6.rs @@ -0,0 +1,16 @@ +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + #[test] + fn indexing_tuple() { + let numbers = (1, 2, 3); + + // Tuple indexing syntax. + let second = numbers.1; + + assert_eq!(second, 2, "This is not the 2nd number in the tuple!"); + } +} diff --git a/solutions/05_vecs/vecs1.rs b/solutions/05_vecs/vecs1.rs new file mode 100644 index 0000000..55b5676 --- /dev/null +++ b/solutions/05_vecs/vecs1.rs @@ -0,0 +1,23 @@ +fn array_and_vec() -> ([i32; 4], Vec<i32>) { + 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 new file mode 100644 index 0000000..87f7625 --- /dev/null +++ b/solutions/05_vecs/vecs2.rs @@ -0,0 +1,55 @@ +fn vec_loop(input: &[i32]) -> Vec<i32> { + let mut output = Vec::new(); + + for element in input { + output.push(2 * element); + } + + output +} + +fn vec_map_example(input: &[i32]) -> Vec<i32> { + // 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<i32> { + // 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 new file mode 100644 index 0000000..ac34e7a --- /dev/null +++ b/solutions/06_move_semantics/move_semantics1.rs @@ -0,0 +1,25 @@ +fn fill_vec(vec: Vec<i32>) -> Vec<i32> { + 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 new file mode 100644 index 0000000..7bcd33a --- /dev/null +++ b/solutions/06_move_semantics/move_semantics2.rs @@ -0,0 +1,28 @@ +fn fill_vec(vec: Vec<i32>) -> Vec<i32> { + 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 new file mode 100644 index 0000000..7ba4006 --- /dev/null +++ b/solutions/06_move_semantics/move_semantics3.rs @@ -0,0 +1,22 @@ +fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> { + // ^^^ 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 new file mode 100644 index 0000000..b7919ac --- /dev/null +++ b/solutions/06_move_semantics/move_semantics4.rs @@ -0,0 +1,21 @@ +fn main() { + // 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_semantics5() { + let mut x = 100; + let y = &mut x; + // `y` used here. + *y += 100; + // The mutable reference `y` is not used anymore, + // therefore a new reference can be created. + let z = &mut x; + *z += 1000; + assert_eq!(x, 1200); + } +} diff --git a/solutions/06_move_semantics/move_semantics5.rs b/solutions/06_move_semantics/move_semantics5.rs new file mode 100644 index 0000000..678ec97 --- /dev/null +++ b/solutions/06_move_semantics/move_semantics5.rs @@ -0,0 +1,23 @@ +#![allow(clippy::ptr_arg)] + +fn main() { + let data = "Rust is great!".to_string(); + + get_char(&data); + + string_uppercase(data); +} + +// 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}"); +} diff --git a/solutions/07_structs/structs1.rs b/solutions/07_structs/structs1.rs new file mode 100644 index 0000000..98fafcc --- /dev/null +++ b/solutions/07_structs/structs1.rs @@ -0,0 +1,49 @@ +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 new file mode 100644 index 0000000..589dd93 --- /dev/null +++ b/solutions/07_structs/structs2.rs @@ -0,0 +1,51 @@ +#[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 new file mode 100644 index 0000000..3f878cc --- /dev/null +++ b/solutions/07_structs/structs3.rs @@ -0,0 +1,83 @@ +#[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 new file mode 100644 index 0000000..9724883 --- /dev/null +++ b/solutions/08_enums/enums1.rs @@ -0,0 +1,14 @@ +#[derive(Debug)] +enum Message { + Quit, + Echo, + Move, + ChangeColor, +} + +fn main() { + println!("{:?}", Message::Quit); + println!("{:?}", Message::Echo); + println!("{:?}", Message::Move); + println!("{:?}", Message::ChangeColor); +} diff --git a/solutions/08_enums/enums2.rs b/solutions/08_enums/enums2.rs new file mode 100644 index 0000000..b19394c --- /dev/null +++ b/solutions/08_enums/enums2.rs @@ -0,0 +1,27 @@ +#[allow(dead_code)] +#[derive(Debug)] +enum Message { + Move { x: i64, y: i64 }, + Echo(String), + ChangeColor(u8, u8, u8), + Quit, +} + +impl Message { + fn call(&self) { + println!("{self:?}"); + } +} + +fn main() { + let messages = [ + Message::Move { x: 10, y: 30 }, + 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 new file mode 100644 index 0000000..8baa25c --- /dev/null +++ b/solutions/08_enums/enums3.rs @@ -0,0 +1,75 @@ +enum Message { + ChangeColor(u8, u8, u8), + Echo(String), + Move(Point), + Quit, +} + +struct Point { + x: u8, + y: u8, +} + +struct State { + color: (u8, u8, u8), + position: Point, + quit: bool, + message: String, +} + +impl State { + fn change_color(&mut self, color: (u8, u8, u8)) { + self.color = color; + } + + fn quit(&mut self) { + self.quit = true; + } + + fn echo(&mut self, s: String) { + self.message = s; + } + + fn move_position(&mut self, point: Point) { + self.position = point; + } + + fn process(&mut self, message: Message) { + match message { + Message::ChangeColor(r, g, b) => self.change_color((r, g, b)), + Message::Echo(s) => self.echo(s), + Message::Move(point) => self.move_position(point), + 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 { + quit: false, + position: Point { x: 0, y: 0 }, + color: (0, 0, 0), + message: String::from("hello world"), + }; + + state.process(Message::ChangeColor(255, 0, 255)); + state.process(Message::Echo(String::from("Hello world!"))); + state.process(Message::Move(Point { x: 10, y: 15 })); + state.process(Message::Quit); + + assert_eq!(state.color, (255, 0, 255)); + assert_eq!(state.position.x, 10); + assert_eq!(state.position.y, 15); + assert!(state.quit); + assert_eq!(state.message, "Hello world!"); + } +} diff --git a/solutions/09_strings/strings1.rs b/solutions/09_strings/strings1.rs new file mode 100644 index 0000000..f7ba811 --- /dev/null +++ b/solutions/09_strings/strings1.rs @@ -0,0 +1,9 @@ +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 new file mode 100644 index 0000000..7de311f --- /dev/null +++ b/solutions/09_strings/strings2.rs @@ -0,0 +1,15 @@ +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 new file mode 100644 index 0000000..a478e62 --- /dev/null +++ b/solutions/09_strings/strings3.rs @@ -0,0 +1,48 @@ +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 new file mode 100644 index 0000000..9dc6917 --- /dev/null +++ b/solutions/09_strings/strings4.rs @@ -0,0 +1,38 @@ +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`. + // But if is called where `&str` is expected, then `&str` is kept `&str` since no + // conversion is needed. + string("nice weather".into()); + string_slice("nice weather".into()); + // ^^^^^^^ the compiler recommends removing the `.into()` + // call because it is a useless conversion. + + 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 new file mode 100644 index 0000000..873b412 --- /dev/null +++ b/solutions/10_modules/modules1.rs @@ -0,0 +1,15 @@ +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 new file mode 100644 index 0000000..298d76e --- /dev/null +++ b/solutions/10_modules/modules2.rs @@ -0,0 +1,24 @@ +#[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 new file mode 100644 index 0000000..99ff5a7 --- /dev/null +++ b/solutions/10_modules/modules3.rs @@ -0,0 +1,8 @@ +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/solutions/11_hashmaps/hashmaps1.rs b/solutions/11_hashmaps/hashmaps1.rs new file mode 100644 index 0000000..3a787c4 --- /dev/null +++ b/solutions/11_hashmaps/hashmaps1.rs @@ -0,0 +1,42 @@ +// 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<String, u32> { + // 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::<u32>() >= 5); + } +} diff --git a/solutions/11_hashmaps/hashmaps2.rs b/solutions/11_hashmaps/hashmaps2.rs new file mode 100644 index 0000000..a5e6ef9 --- /dev/null +++ b/solutions/11_hashmaps/hashmaps2.rs @@ -0,0 +1,95 @@ +// 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 these fruits! + +use std::collections::HashMap; + +#[derive(Hash, PartialEq, Eq, Debug)] +enum Fruit { + Apple, + Banana, + Mango, + Lychee, + Pineapple, +} + +fn fruit_basket(basket: &mut HashMap<Fruit, u32>) { + 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<Fruit, u32> { + 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::<u32>(); + 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 new file mode 100644 index 0000000..54f480b --- /dev/null +++ b/solutions/11_hashmaps/hashmaps3.rs @@ -0,0 +1,83 @@ +// A list of scores (one per line) of a soccer match is given. Each line is of +// the form "<team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>" +// 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 Team { + goals_scored: u8, + goals_conceded: u8, +} + +fn build_scores_table(results: &str) -> HashMap<&str, Team> { + // 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(Team::default); + // Update the values. + team_1.goals_scored += team_1_score; + team_1.goals_conceded += team_2_score; + + // Similarely for the second team. + let team_2 = scores.entry(team_2_name).or_insert_with(Team::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 new file mode 100644 index 0000000..4d615dd --- /dev/null +++ b/solutions/12_options/options1.rs @@ -0,0 +1,39 @@ +// 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<u16> { + 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 new file mode 100644 index 0000000..0f24665 --- /dev/null +++ b/solutions/12_options/options2.rs @@ -0,0 +1,37 @@ +fn main() { + // 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<Option<i8>> = 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 new file mode 100644 index 0000000..0081eeb --- /dev/null +++ b/solutions/12_options/options3.rs @@ -0,0 +1,26 @@ +#[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 new file mode 100644 index 0000000..f552ca7 --- /dev/null +++ b/solutions/13_error_handling/errors1.rs @@ -0,0 +1,37 @@ +fn generate_nametag_text(name: String) -> Result<String, String> { + // ^^^^^^ ^^^^^^ + 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 new file mode 100644 index 0000000..0597c8c --- /dev/null +++ b/solutions/13_error_handling/errors2.rs @@ -0,0 +1,58 @@ +// 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<i32, ParseIntError> { + let processing_fee = 1; + let cost_per_item = 5; + + // Added `?` to propagate the error. + let qty = item_quantity.parse::<i32>()?; + // ^ added + + // Equivalent to this verbose version: + let qty = match item_quantity.parse::<i32>() { + 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 new file mode 100644 index 0000000..63f4aba --- /dev/null +++ b/solutions/13_error_handling/errors3.rs @@ -0,0 +1,32 @@ +// 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<i32, ParseIntError> { + let processing_fee = 1; + let cost_per_item = 5; + let qty = item_quantity.parse::<i32>()?; + + 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 new file mode 100644 index 0000000..f4d39bf --- /dev/null +++ b/solutions/13_error_handling/errors4.rs @@ -0,0 +1,44 @@ +#![allow(clippy::comparison_chain)] + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result<Self, CreationError> { + if value == 0 { + Err(CreationError::Zero) + } else if value < 0 { + Err(CreationError::Negative) + } else { + 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 new file mode 100644 index 0000000..c1424ee --- /dev/null +++ b/solutions/13_error_handling/errors5.rs @@ -0,0 +1,54 @@ +// 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<dyn ???>` 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<dyn Trait>` 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<PositiveNonzeroInteger, CreationError> { + match value { + x if x < 0 => Err(CreationError::Negative), + 0 => Err(CreationError::Zero), + x => Ok(PositiveNonzeroInteger(x as u64)), + } + } +} + +fn main() -> Result<(), Box<dyn Error>> { + let pretend_user_input = "42"; + let x: i64 = pretend_user_input.parse()?; + println!("output={:?}", PositiveNonzeroInteger::new(x)?); + Ok(()) +} diff --git a/solutions/13_error_handling/errors6.rs b/solutions/13_error_handling/errors6.rs new file mode 100644 index 0000000..429d3ea --- /dev/null +++ b/solutions/13_error_handling/errors6.rs @@ -0,0 +1,91 @@ +// Using catch-all error types like `Box<dyn Error>` isn't recommended for +// library code where callers might want to make decisions based on the error +// content instead of printing it out or propagating it further. Here, we define +// a custom error type to make it possible for callers to decide what to do next +// when our function returns an error. + +use std::num::ParseIntError; + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +// A custom error type that we will be using in `PositiveNonzeroInteger::parse`. +#[derive(PartialEq, Debug)] +enum ParsePosNonzeroError { + Creation(CreationError), + ParseInt(ParseIntError), +} + +impl ParsePosNonzeroError { + fn from_creation(err: CreationError) -> Self { + Self::Creation(err) + } + + fn from_parseint(err: ParseIntError) -> Self { + Self::ParseInt(err) + } +} + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result<Self, CreationError> { + match value { + x if x < 0 => Err(CreationError::Negative), + 0 => Err(CreationError::Zero), + x => Ok(Self(x as u64)), + } + } + + fn parse(s: &str) -> Result<Self, ParsePosNonzeroError> { + // Return an appropriate error instead of panicking when `parse()` + // returns an error. + let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?; + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Self::new(x).map_err(ParsePosNonzeroError::from_creation) + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse_error() { + assert!(matches!( + PositiveNonzeroInteger::parse("not a number"), + Err(ParsePosNonzeroError::ParseInt(_)), + )); + } + + #[test] + fn test_negative() { + assert_eq!( + PositiveNonzeroInteger::parse("-555"), + Err(ParsePosNonzeroError::Creation(CreationError::Negative)), + ); + } + + #[test] + fn test_zero() { + assert_eq!( + PositiveNonzeroInteger::parse("0"), + Err(ParsePosNonzeroError::Creation(CreationError::Zero)), + ); + } + + #[test] + fn test_positive() { + let x = PositiveNonzeroInteger::new(42).unwrap(); + assert_eq!(x.0, 42); + assert_eq!(PositiveNonzeroInteger::parse("42"), Ok(x)); + } +} diff --git a/solutions/14_generics/generics1.rs b/solutions/14_generics/generics1.rs new file mode 100644 index 0000000..e2195fd --- /dev/null +++ b/solutions/14_generics/generics1.rs @@ -0,0 +1,17 @@ +// `Vec<T>` is generic over the type `T`. In most cases, the compiler is able to +// infer `T`, for example after pushing a value with a concrete type to the vector. +// But in this exercise, the compiler needs some help through a type annotation. + +fn main() { + // `u8` and `i8` can both be converted to `i16`. + let mut numbers: Vec<i16> = Vec::new(); + // ^^^^^^^^^^ added + + // Don't change the lines below. + let n1: u8 = 42; + numbers.push(n1.into()); + let n2: i8 = -1; + numbers.push(n2.into()); + + println!("{numbers:?}"); +} diff --git a/solutions/14_generics/generics2.rs b/solutions/14_generics/generics2.rs new file mode 100644 index 0000000..14f3f7a --- /dev/null +++ b/solutions/14_generics/generics2.rs @@ -0,0 +1,28 @@ +struct Wrapper<T> { + value: T, +} + +impl<T> Wrapper<T> { + 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"); + } +} diff --git a/solutions/15_traits/traits1.rs b/solutions/15_traits/traits1.rs new file mode 100644 index 0000000..790873f --- /dev/null +++ b/solutions/15_traits/traits1.rs @@ -0,0 +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() { + 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 new file mode 100644 index 0000000..0db93e0 --- /dev/null +++ b/solutions/15_traits/traits2.rs @@ -0,0 +1,27 @@ +trait AppendBar { + fn append_bar(self) -> Self; +} + +impl AppendBar for Vec<String> { + 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"); + } +} diff --git a/solutions/15_traits/traits3.rs b/solutions/15_traits/traits3.rs new file mode 100644 index 0000000..747d919 --- /dev/null +++ b/solutions/15_traits/traits3.rs @@ -0,0 +1,38 @@ +#![allow(dead_code)] + +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); + } +} diff --git a/solutions/15_traits/traits4.rs b/solutions/15_traits/traits4.rs new file mode 100644 index 0000000..3675b8d --- /dev/null +++ b/solutions/15_traits/traits4.rs @@ -0,0 +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() { + // 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 new file mode 100644 index 0000000..1fb426a --- /dev/null +++ b/solutions/15_traits/traits5.rs @@ -0,0 +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() { + // 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 new file mode 100644 index 0000000..ca7b688 --- /dev/null +++ b/solutions/16_lifetimes/lifetimes1.rs @@ -0,0 +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() { + // 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 new file mode 100644 index 0000000..b0f2ef1 --- /dev/null +++ b/solutions/16_lifetimes/lifetimes2.rs @@ -0,0 +1,33 @@ +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). + } +} diff --git a/solutions/16_lifetimes/lifetimes3.rs b/solutions/16_lifetimes/lifetimes3.rs new file mode 100644 index 0000000..16a5a68 --- /dev/null +++ b/solutions/16_lifetimes/lifetimes3.rs @@ -0,0 +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() { + 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 new file mode 100644 index 0000000..c52b8b1 --- /dev/null +++ b/solutions/17_tests/tests1.rs @@ -0,0 +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() { + // 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 new file mode 100644 index 0000000..39a0005 --- /dev/null +++ b/solutions/17_tests/tests2.rs @@ -0,0 +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() { + // 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 new file mode 100644 index 0000000..503f9bc --- /dev/null +++ b/solutions/17_tests/tests3.rs @@ -0,0 +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 can't be negative"); + } + + 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); + } +} diff --git a/solutions/18_iterators/iterators1.rs b/solutions/18_iterators/iterators1.rs new file mode 100644 index 0000000..93a6008 --- /dev/null +++ b/solutions/18_iterators/iterators1.rs @@ -0,0 +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() { + // 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 new file mode 100644 index 0000000..db05f29 --- /dev/null +++ b/solutions/18_iterators/iterators2.rs @@ -0,0 +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<String> { + 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"); + } +} diff --git a/solutions/18_iterators/iterators3.rs b/solutions/18_iterators/iterators3.rs new file mode 100644 index 0000000..d66d1ef --- /dev/null +++ b/solutions/18_iterators/iterators3.rs @@ -0,0 +1,73 @@ +#[derive(Debug, PartialEq, Eq)] +enum DivisionError { + DivideByZero, + NotDivisible, +} + +fn divide(a: i64, b: i64) -> Result<i64, DivisionError> { + if b == 0 { + return Err(DivisionError::DivideByZero); + } + + if a % b != 0 { + return Err(DivisionError::NotDivisible); + } + + Ok(a / b) +} + +fn result_with_list() -> Result<Vec<i64>, 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<Result<i64, DivisionError>> { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 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_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 new file mode 100644 index 0000000..4c3c49d --- /dev/null +++ b/solutions/18_iterators/iterators4.rs @@ -0,0 +1,71 @@ +// 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… + (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); + } +} diff --git a/solutions/18_iterators/iterators5.rs b/solutions/18_iterators/iterators5.rs new file mode 100644 index 0000000..85d9a4f --- /dev/null +++ b/solutions/18_iterators/iterators5.rs @@ -0,0 +1,183 @@ +// 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<String, Progress>, value: Progress) -> usize { + let mut count = 0; + for val in map.values() { + if *val == value { + count += 1; + } + } + count +} + +fn count_iterator(map: &HashMap<String, Progress>, 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<String, Progress>], value: Progress) -> usize { + let mut count = 0; + for map in collection { + count += count_for(map, value); + } + count +} + +fn count_collection_iterator(collection: &[HashMap<String, Progress>], 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`+`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<String, Progress>], + 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::*; + + fn get_map() -> HashMap<String, Progress> { + use Progress::*; + + 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<HashMap<String, Progress>> { + use Progress::*; + + 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, Progress::Complete), 3); + } + + #[test] + fn count_some() { + let map = get_map(); + assert_eq!(count_iterator(&map, Progress::Some), 1); + } + + #[test] + fn count_none() { + let map = get_map(); + assert_eq!(count_iterator(&map, Progress::None), 2); + } + + #[test] + fn count_complete_equals_for() { + let map = get_map(); + let progress_states = [Progress::Complete, Progress::Some, Progress::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, Progress::Complete), + 6, + ); + assert_eq!( + count_collection_iterator_flat(&collection, Progress::Complete), + 6, + ); + } + + #[test] + fn count_collection_some() { + let collection = get_vec_map(); + assert_eq!(count_collection_iterator(&collection, Progress::Some), 1); + assert_eq!( + count_collection_iterator_flat(&collection, Progress::Some), + 1 + ); + } + + #[test] + fn count_collection_none() { + let collection = get_vec_map(); + assert_eq!(count_collection_iterator(&collection, Progress::None), 4); + assert_eq!( + count_collection_iterator_flat(&collection, Progress::None), + 4 + ); + } + + #[test] + fn count_collection_equals_for() { + let collection = get_vec_map(); + let progress_states = [Progress::Complete, Progress::Some, Progress::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/19_smart_pointers/arc1.rs b/solutions/19_smart_pointers/arc1.rs new file mode 100644 index 0000000..bd76189 --- /dev/null +++ b/solutions/19_smart_pointers/arc1.rs @@ -0,0 +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() { + 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 new file mode 100644 index 0000000..189cc56 --- /dev/null +++ b/solutions/19_smart_pointers/box1.rs @@ -0,0 +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<List>), + 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()); + } +} diff --git a/solutions/19_smart_pointers/cow1.rs b/solutions/19_smart_pointers/cow1.rs new file mode 100644 index 0000000..0a21a91 --- /dev/null +++ b/solutions/19_smart_pointers/cow1.rs @@ -0,0 +1,68 @@ +// 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 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. 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 new file mode 100644 index 0000000..512eb9c --- /dev/null +++ b/solutions/19_smart_pointers/rc1.rs @@ -0,0 +1,105 @@ +// In this exercise, we want to express the concept of multiple owners via the +// `Rc<T>` 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; + +#[allow(dead_code)] +#[derive(Debug)] +enum Planet { + Mercury(Rc<Sun>), + Venus(Rc<Sun>), + Earth(Rc<Sun>), + Mars(Rc<Sun>), + Jupiter(Rc<Sun>), + Saturn(Rc<Sun>), + Uranus(Rc<Sun>), + Neptune(Rc<Sun>), +} + +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(); + + // 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 new file mode 100644 index 0000000..7f3dd29 --- /dev/null +++ b/solutions/20_threads/threads1.rs @@ -0,0 +1,37 @@ +// This program spawns multiple threads that each run for at least 250ms, and +// each thread returns how much time they 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"); + } +} diff --git a/solutions/20_threads/threads2.rs b/solutions/20_threads/threads2.rs new file mode 100644 index 0000000..bc268d6 --- /dev/null +++ b/solutions/20_threads/threads2.rs @@ -0,0 +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() { + // `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 new file mode 100644 index 0000000..cd2dfbe --- /dev/null +++ b/solutions/20_threads/threads3.rs @@ -0,0 +1,66 @@ +use std::{sync::mpsc, thread, time::Duration}; + +struct Queue { + length: u32, + first_half: Vec<u32>, + second_half: Vec<u32>, +} + +impl Queue { + fn new() -> Self { + Self { + length: 10, + first_half: vec![1, 2, 3, 4, 5], + second_half: vec![6, 7, 8, 9, 10], + } + } +} + +fn send_tx(q: Queue, tx: mpsc::Sender<u32>) { + // 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(); + let queue_length = queue.length; + + send_tx(queue, tx); + + let mut total_received: u32 = 0; + for received in rx { + println!("Got: {received}"); + total_received += 1; + } + + println!("Number of received values: {total_received}"); + assert_eq!(total_received, queue_length); + } +} diff --git a/solutions/21_macros/macros1.rs b/solutions/21_macros/macros1.rs new file mode 100644 index 0000000..1b86156 --- /dev/null +++ b/solutions/21_macros/macros1.rs @@ -0,0 +1,10 @@ +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; +} + +fn main() { + my_macro!(); + // ^ +} diff --git a/solutions/21_macros/macros2.rs b/solutions/21_macros/macros2.rs new file mode 100644 index 0000000..b6fd5d2 --- /dev/null +++ b/solutions/21_macros/macros2.rs @@ -0,0 +1,10 @@ +// Moved the macro definition to be before its call. +macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; +} + +fn main() { + my_macro!(); +} diff --git a/solutions/21_macros/macros3.rs b/solutions/21_macros/macros3.rs new file mode 100644 index 0000000..df35be4 --- /dev/null +++ b/solutions/21_macros/macros3.rs @@ -0,0 +1,13 @@ +// Added the attribute `macro_use` attribute. +#[macro_use] +mod macros { + macro_rules! my_macro { + () => { + println!("Check out my macro!"); + }; + } +} + +fn main() { + my_macro!(); +} diff --git a/solutions/21_macros/macros4.rs b/solutions/21_macros/macros4.rs new file mode 100644 index 0000000..41bcad1 --- /dev/null +++ b/solutions/21_macros/macros4.rs @@ -0,0 +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() { + my_macro!(); + my_macro!(7777); +} diff --git a/solutions/22_clippy/clippy1.rs b/solutions/22_clippy/clippy1.rs new file mode 100644 index 0000000..b9d1ec1 --- /dev/null +++ b/solutions/22_clippy/clippy1.rs @@ -0,0 +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() { + // 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 new file mode 100644 index 0000000..7f63562 --- /dev/null +++ b/solutions/22_clippy/clippy2.rs @@ -0,0 +1,10 @@ +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}"); +} diff --git a/solutions/22_clippy/clippy3.rs b/solutions/22_clippy/clippy3.rs new file mode 100644 index 0000000..811d184 --- /dev/null +++ b/solutions/22_clippy/clippy3.rs @@ -0,0 +1,31 @@ +use std::mem; + +#[rustfmt::skip] +#[allow(unused_variables, unused_assignments)] +fn main() { + 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 new file mode 100644 index 0000000..91b12ba --- /dev/null +++ b/solutions/23_conversions/as_ref_mut.rs @@ -0,0 +1,59 @@ +// 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. +fn byte_counter<T: AsRef<str>>(arg: T) -> usize { + arg.as_ref().as_bytes().len() +} + +// Obtain the number of characters (not bytes) in the given argument. +fn char_counter<T: AsRef<str>>(arg: T) -> usize { + arg.as_ref().chars().count() +} + +// Squares a number using `as_mut()`. +fn num_sq<T: AsMut<u32>>(arg: &mut T) { + let arg = arg.as_mut(); + *arg = *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<u32> = 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 new file mode 100644 index 0000000..cec23cb --- /dev/null +++ b/solutions/23_conversions/from_into.rs @@ -0,0 +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() { + // 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 new file mode 100644 index 0000000..005b501 --- /dev/null +++ b/solutions/23_conversions/from_str.rs @@ -0,0 +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::<u8>() + ParseInt(ParseIntError), +} + +impl FromStr for Person { + type Err = ParsePersonError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + 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::<Person>(); + println!("{p:?}"); +} + +#[cfg(test)] +mod tests { + use super::*; + use ParsePersonError::*; + + #[test] + fn empty_input() { + assert_eq!("".parse::<Person>(), Err(BadLen)); + } + + #[test] + fn good_input() { + let p = "John,32".parse::<Person>(); + 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::<Person>(), Err(ParseInt(_)))); + } + + #[test] + fn invalid_age() { + assert!(matches!("John,twenty".parse::<Person>(), Err(ParseInt(_)))); + } + + #[test] + fn missing_comma_and_age() { + assert_eq!("John".parse::<Person>(), Err(BadLen)); + } + + #[test] + fn missing_name() { + assert_eq!(",1".parse::<Person>(), Err(NoName)); + } + + #[test] + fn missing_name_and_age() { + assert!(matches!(",".parse::<Person>(), Err(NoName | ParseInt(_)))); + } + + #[test] + fn missing_name_and_invalid_age() { + assert!(matches!( + ",one".parse::<Person>(), + Err(NoName | ParseInt(_)), + )); + } + + #[test] + fn trailing_comma() { + assert_eq!("John,32,".parse::<Person>(), Err(BadLen)); + } + + #[test] + fn trailing_comma_and_some_string() { + assert_eq!("John,32,man".parse::<Person>(), Err(BadLen)); + } +} diff --git a/solutions/23_conversions/try_from_into.rs b/solutions/23_conversions/try_from_into.rs new file mode 100644 index 0000000..ee802eb --- /dev/null +++ b/solutions/23_conversions/try_from_into.rs @@ -0,0 +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<Self, Self::Error> { + 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<Self, Self::Error> { + // 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<Self, Self::Error> { + // 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<Color, _> = [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<Color, _> = (&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<Color, _> = (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<Color, _> = [1000, 10000, 256].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_out_of_range_negative() { + let c: Result<Color, _> = [-10, -256, -1].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_sum() { + let c: Result<Color, _> = [-1, 255, 255].try_into(); + assert_eq!(c, Err(IntConversion)); + } + + #[test] + fn test_array_correct() { + let c: Result<Color, _> = [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, _> = 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 new file mode 100644 index 0000000..14b92eb --- /dev/null +++ b/solutions/23_conversions/using_as.rs @@ -0,0 +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::<f64>(); + 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); + } +} diff --git a/solutions/quizzes/quiz1.rs b/solutions/quizzes/quiz1.rs new file mode 100644 index 0000000..bc76166 --- /dev/null +++ b/solutions/quizzes/quiz1.rs @@ -0,0 +1,31 @@ +// Mary is buying apples. The price of an apple is calculated as follows: +// - An apple costs 2 rustbucks. +// - If Mary buys more than 40 apples, each apple only costs 1 rustbuck! +// Write a function that calculates the price of an order of apples given the +// quantity bought. + +fn calculate_price_of_apples(n_apples: u64) -> u64 { + if n_apples > 40 { + n_apples + } else { + 2 * n_apples + } +} + +fn main() { + // You can optionally experiment here. +} + +// Don't change the tests! +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn verify_test() { + assert_eq!(calculate_price_of_apples(35), 70); + assert_eq!(calculate_price_of_apples(40), 80); + assert_eq!(calculate_price_of_apples(41), 41); + assert_eq!(calculate_price_of_apples(65), 65); + } +} diff --git a/solutions/quizzes/quiz2.rs b/solutions/quizzes/quiz2.rs new file mode 100644 index 0000000..0d2a513 --- /dev/null +++ b/solutions/quizzes/quiz2.rs @@ -0,0 +1,107 @@ +// This is a quiz for the following sections: +// - Strings +// - Vecs +// - Move semantics +// - Modules +// - Enums +// +// 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 a 2-length tuple, +// 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<String> { + let mut output = Vec::new(); + + for (mut 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) => { + for _ in 0..n { + string += "bar"; + } + string + } + }; + + // 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<String> { + input + .into_iter() + .map(|(mut string, command)| match command { + Command::Uppercase => string.to_uppercase(), + Command::Trim => string.trim().to_string(), + Command::Append(n) => { + for _ in 0..n { + string += "bar"; + } + string + } + }) + .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", + ] + ); + } + } +} diff --git a/solutions/quizzes/quiz3.rs b/solutions/quizzes/quiz3.rs new file mode 100644 index 0000000..e3413fd --- /dev/null +++ b/solutions/quizzes/quiz3.rs @@ -0,0 +1,69 @@ +// This quiz tests: +// - Generics +// - Traits +// +// 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<T> { + // ^^^ + grade: T, + // ^ + student_name: String, + student_age: u8, +} + +// To be able to print the grade, it has to implement the `Display` trait. +impl<T: Display> ReportCard<T> { + // ^^^^^^^ 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+", + ); + } +} |
