From b1b81f7e071c2629759ff9a594d34e10ef29f183 Mon Sep 17 00:00:00 2001 From: Samuele Iacoponi Date: Sun, 1 Feb 2026 16:38:57 +0100 Subject: [PATCH] Completed exercise up to Traits --- .rustlings-state.txt | 21 ++++- exercises/12_options/options1.rs | 19 ++++- exercises/12_options/options2.rs | 17 +++- exercises/12_options/options3.rs | 2 +- exercises/13_error_handling/errors1.rs | 8 +- exercises/13_error_handling/errors2.rs | 9 ++- exercises/13_error_handling/errors3.rs | 4 +- exercises/13_error_handling/errors4.rs | 8 +- exercises/13_error_handling/errors5.rs | 2 +- exercises/13_error_handling/errors6.rs | 5 +- exercises/14_generics/generics1.rs | 2 +- exercises/14_generics/generics2.rs | 8 +- exercises/15_traits/traits1.rs | 8 ++ exercises/15_traits/traits2.rs | 7 ++ exercises/15_traits/traits3.rs | 4 +- exercises/15_traits/traits4.rs | 3 +- exercises/15_traits/traits5.rs | 2 +- solutions/12_options/options1.rs | 41 +++++++++- solutions/12_options/options2.rs | 37 ++++++++- solutions/12_options/options3.rs | 29 ++++++- solutions/13_error_handling/errors1.rs | 39 ++++++++- solutions/13_error_handling/errors2.rs | 60 +++++++++++++- solutions/13_error_handling/errors3.rs | 34 +++++++- solutions/13_error_handling/errors4.rs | 44 +++++++++- solutions/13_error_handling/errors5.rs | 56 ++++++++++++- solutions/13_error_handling/errors6.rs | 108 ++++++++++++++++++++++++- solutions/14_generics/generics1.rs | 17 +++- solutions/14_generics/generics2.rs | 30 ++++++- solutions/15_traits/traits1.rs | 34 +++++++- solutions/15_traits/traits2.rs | 29 ++++++- solutions/15_traits/traits3.rs | 38 ++++++++- solutions/15_traits/traits4.rs | 37 ++++++++- solutions/15_traits/traits5.rs | 41 +++++++++- 33 files changed, 732 insertions(+), 71 deletions(-) diff --git a/.rustlings-state.txt b/.rustlings-state.txt index 0fbae48..0d0fc5e 100644 --- a/.rustlings-state.txt +++ b/.rustlings-state.txt @@ -1,6 +1,6 @@ DON'T EDIT THIS FILE! -quiz2 +quiz3 intro1 intro2 @@ -47,4 +47,21 @@ modules2 modules3 hashmaps1 hashmaps2 -hashmaps3 \ No newline at end of file +hashmaps3 +quiz2 +options1 +options2 +options3 +errors1 +errors2 +errors3 +errors4 +errors5 +errors6 +generics1 +generics2 +traits1 +traits2 +traits3 +traits4 +traits5 \ No newline at end of file diff --git a/exercises/12_options/options1.rs b/exercises/12_options/options1.rs index d0c412a..90c4203 100644 --- a/exercises/12_options/options1.rs +++ b/exercises/12_options/options1.rs @@ -3,7 +3,22 @@ // someone eats it all, so no ice cream is left (value 0). Return `None` if // `hour_of_day` is higher than 23. fn maybe_ice_cream(hour_of_day: u16) -> Option { - // TODO: Complete the function body. + if hour_of_day > 23 { + None + } else if hour_of_day < 22 { + Some(5) + } else { + Some(0) + } + + // OR + // fn maybe_ice_cream(hour_of_day: u16) -> Option { + // match hour_of_day { + // 0..=21 => Some(5), + // 22 | 23 => Some(0), + // _ => None, + // } + // } } fn main() { @@ -20,7 +35,7 @@ mod tests { // Option? let ice_creams = maybe_ice_cream(12); - assert_eq!(ice_creams, 5); // Don't change this line. + assert_eq!(ice_creams, Some(5)); // Don't change this line. } #[test] diff --git a/exercises/12_options/options2.rs b/exercises/12_options/options2.rs index 07c27c6..1dc084a 100644 --- a/exercises/12_options/options2.rs +++ b/exercises/12_options/options2.rs @@ -10,7 +10,10 @@ mod tests { let optional_target = Some(target); // TODO: Make this an if-let statement whose value is `Some`. - word = optional_target { + // word = optional_target { + // assert_eq!(word, target); + // } + if let Some(word) = optional_target { assert_eq!(word, target); } } @@ -29,10 +32,16 @@ mod tests { // TODO: Make this a while-let statement. Remember that `Vec::pop()` // adds another layer of `Option`. You can do nested pattern matching // in if-let and while-let statements. - integer = optional_integers.pop() { - assert_eq!(integer, cursor); - cursor -= 1; + while let Some(integer) = optional_integers.pop() { + if let Some(integer_value) = integer { + println!("integer_value (left): {}, cursor (right): {}", integer_value, cursor); + assert_eq!(integer_value, cursor); + cursor -= 1; + } else { + println!("None value, cursor: {}", cursor); + } } + assert_eq!(cursor, 0); } diff --git a/exercises/12_options/options3.rs b/exercises/12_options/options3.rs index c97b1d3..aecb7a5 100644 --- a/exercises/12_options/options3.rs +++ b/exercises/12_options/options3.rs @@ -9,7 +9,7 @@ fn main() { // TODO: Fix the compiler error by adding something to this match statement. match optional_point { - Some(p) => println!("Coordinates are {},{}", p.x, p.y), + Some(ref p) => println!("Coordinates are {},{}", p.x, p.y), _ => panic!("No match!"), } diff --git a/exercises/13_error_handling/errors1.rs b/exercises/13_error_handling/errors1.rs index e07fddc..37d2599 100644 --- a/exercises/13_error_handling/errors1.rs +++ b/exercises/13_error_handling/errors1.rs @@ -4,12 +4,14 @@ // construct to `Option` that can be used to express error conditions. Change // the function signature and body to return `Result` instead // of `Option`. -fn generate_nametag_text(name: String) -> Option { +fn generate_nametag_text(name: String) -> Result { if name.is_empty() { // Empty names aren't allowed - None + // None + Err("Empty names aren't allowed".to_string()) } else { - Some(format!("Hi! My name is {name}")) + // Some(format!("Hi! My name is {name}")) + Ok(format!("Hi! My name is {name}")) } } diff --git a/exercises/13_error_handling/errors2.rs b/exercises/13_error_handling/errors2.rs index defe359..92f36d0 100644 --- a/exercises/13_error_handling/errors2.rs +++ b/exercises/13_error_handling/errors2.rs @@ -21,7 +21,14 @@ fn total_cost(item_quantity: &str) -> Result { let cost_per_item = 5; // TODO: Handle the error case as described above. - let qty = item_quantity.parse::(); + let qty = item_quantity.parse::()?; + + // OR + // let qty_result = item_quantity.parse::(); + // let qty = match qty_result { + // Ok(number) => number, + // Err(e) => return Err(e), + // }; Ok(qty * cost_per_item + processing_fee) } diff --git a/exercises/13_error_handling/errors3.rs b/exercises/13_error_handling/errors3.rs index 8e8c38a..7854973 100644 --- a/exercises/13_error_handling/errors3.rs +++ b/exercises/13_error_handling/errors3.rs @@ -15,7 +15,7 @@ fn total_cost(item_quantity: &str) -> Result { // TODO: Fix the compiler error by changing the signature and body of the // `main` function. -fn main() { +fn main() -> Result<(), ParseIntError>{ let mut tokens = 100; let pretend_user_input = "8"; @@ -28,4 +28,6 @@ fn main() { tokens -= cost; println!("You now have {tokens} tokens."); } + + Ok(()) } diff --git a/exercises/13_error_handling/errors4.rs b/exercises/13_error_handling/errors4.rs index 144fce7..4ab68d4 100644 --- a/exercises/13_error_handling/errors4.rs +++ b/exercises/13_error_handling/errors4.rs @@ -11,7 +11,13 @@ impl PositiveNonzeroInteger { fn new(value: i64) -> Result { // TODO: This function shouldn't always return an `Ok`. // Read the tests below to clarify what should be returned. - Ok(Self(value as u64)) + if value < 0 { + Err(CreationError::Negative) + } else if value == 0 { + Err(CreationError::Zero) + } else { + Ok(Self(value as u64)) + } } } diff --git a/exercises/13_error_handling/errors5.rs b/exercises/13_error_handling/errors5.rs index 125779b..d33d12e 100644 --- a/exercises/13_error_handling/errors5.rs +++ b/exercises/13_error_handling/errors5.rs @@ -48,7 +48,7 @@ impl PositiveNonzeroInteger { // TODO: Add the correct return type `Result<(), Box>`. What can we // use to describe both errors? Is there a trait which both errors implement? -fn main() { +fn main() -> Result<(), Box>{ let pretend_user_input = "42"; let x: i64 = pretend_user_input.parse()?; println!("output={:?}", PositiveNonzeroInteger::new(x)?); diff --git a/exercises/13_error_handling/errors6.rs b/exercises/13_error_handling/errors6.rs index b1995e0..db1382c 100644 --- a/exercises/13_error_handling/errors6.rs +++ b/exercises/13_error_handling/errors6.rs @@ -26,6 +26,9 @@ impl ParsePosNonzeroError { // TODO: Add another error conversion function here. // fn from_parse_int(???) -> Self { ??? } + fn from_parse_int(err: ParseIntError) -> Self { + Self::ParseInt(err) + } } #[derive(PartialEq, Debug)] @@ -43,7 +46,7 @@ impl PositiveNonzeroInteger { fn parse(s: &str) -> Result { // TODO: change this to return an appropriate error instead of panicking // when `parse()` returns an error. - let x: i64 = s.parse().unwrap(); + let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?; Self::new(x).map_err(ParsePosNonzeroError::from_creation) } } diff --git a/exercises/14_generics/generics1.rs b/exercises/14_generics/generics1.rs index 87ed990..e13fd82 100644 --- a/exercises/14_generics/generics1.rs +++ b/exercises/14_generics/generics1.rs @@ -6,7 +6,7 @@ fn main() { // TODO: Fix the compiler error by annotating the type of the vector // `Vec`. Choose `T` as some integer type that can be created from // `u8` and `i8`. - let mut numbers = Vec::new(); + let mut numbers: Vec = Vec::new(); // Don't change the lines below. let n1: u8 = 42; diff --git a/exercises/14_generics/generics2.rs b/exercises/14_generics/generics2.rs index 8908725..ba77625 100644 --- a/exercises/14_generics/generics2.rs +++ b/exercises/14_generics/generics2.rs @@ -1,12 +1,12 @@ // This powerful wrapper provides the ability to store a positive integer value. // TODO: Rewrite it using a generic so that it supports wrapping ANY type. -struct Wrapper { - value: u32, +struct Wrapper { + value: T, } // TODO: Adapt the struct's implementation to be generic over the wrapped value. -impl Wrapper { - fn new(value: u32) -> Self { +impl Wrapper { + fn new(value: T) -> Self { Wrapper { value } } } diff --git a/exercises/15_traits/traits1.rs b/exercises/15_traits/traits1.rs index 85be17e..b93b7a4 100644 --- a/exercises/15_traits/traits1.rs +++ b/exercises/15_traits/traits1.rs @@ -6,6 +6,14 @@ trait AppendBar { impl AppendBar for String { // TODO: Implement `AppendBar` for the type `String`. + fn append_bar(self) -> Self{ + // In this i need to put mut in front of the parameter + // self.push_str("Bar"); + // self + + // This way is more readable + self + "Bar" + } } fn main() { diff --git a/exercises/15_traits/traits2.rs b/exercises/15_traits/traits2.rs index d724dc2..603d7e1 100644 --- a/exercises/15_traits/traits2.rs +++ b/exercises/15_traits/traits2.rs @@ -4,6 +4,13 @@ trait AppendBar { // TODO: Implement the trait `AppendBar` for a vector of strings. // `append_bar` should push the string "Bar" into the vector. +impl AppendBar for Vec { + // TODO: Implement `AppendBar` for the type `String`. + fn append_bar(mut self) -> Self{ + self.push("Bar".to_string()); + self + } +} fn main() { // You can optionally experiment here. diff --git a/exercises/15_traits/traits3.rs b/exercises/15_traits/traits3.rs index c244650..144ac0a 100644 --- a/exercises/15_traits/traits3.rs +++ b/exercises/15_traits/traits3.rs @@ -3,7 +3,9 @@ trait Licensed { // implementors like the two structs below can share that default behavior // without repeating the function. // The default license information should be the string "Default license". - fn licensing_info(&self) -> String; + fn licensing_info(&self) -> String { + String::from("Default license") + } } struct SomeSoftware { diff --git a/exercises/15_traits/traits4.rs b/exercises/15_traits/traits4.rs index 80092a6..d9d8533 100644 --- a/exercises/15_traits/traits4.rs +++ b/exercises/15_traits/traits4.rs @@ -11,7 +11,8 @@ impl Licensed for SomeSoftware {} impl Licensed for OtherSoftware {} // TODO: Fix the compiler error by only changing the signature of this function. -fn compare_license_types(software1: ???, software2: ???) -> bool { +fn compare_license_types(software1: impl Licensed, software2: impl Licensed) -> bool { + // Impl Licensed means to accept every types that implement Licensed software1.licensing_info() == software2.licensing_info() } diff --git a/exercises/15_traits/traits5.rs b/exercises/15_traits/traits5.rs index 5b356ac..3f48437 100644 --- a/exercises/15_traits/traits5.rs +++ b/exercises/15_traits/traits5.rs @@ -19,7 +19,7 @@ impl SomeTrait for OtherStruct {} impl OtherTrait for OtherStruct {} // TODO: Fix the compiler error by only changing the signature of this function. -fn some_func(item: ???) -> bool { +fn some_func(item: impl SomeTrait + OtherTrait) -> bool { item.some_function() && item.other_function() } diff --git a/solutions/12_options/options1.rs b/solutions/12_options/options1.rs index dcf2377..4d615dd 100644 --- a/solutions/12_options/options1.rs +++ b/solutions/12_options/options1.rs @@ -1,4 +1,39 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// This function returns how much icecream there is left in the fridge. +// If it's before 22:00 (24-hour system), then 5 scoops are left. At 22:00, +// someone eats it all, so no icecream is left (value 0). Return `None` if +// `hour_of_day` is higher than 23. +fn maybe_icecream(hour_of_day: u16) -> Option { + match hour_of_day { + 0..=21 => Some(5), + 22..=23 => Some(0), + _ => None, + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn raw_value() { + // Using `unwrap` is fine in a test. + let icecreams = maybe_icecream(12).unwrap(); + + assert_eq!(icecreams, 5); + } + + #[test] + fn check_icecream() { + assert_eq!(maybe_icecream(0), Some(5)); + assert_eq!(maybe_icecream(9), Some(5)); + assert_eq!(maybe_icecream(18), Some(5)); + assert_eq!(maybe_icecream(22), Some(0)); + assert_eq!(maybe_icecream(23), Some(0)); + assert_eq!(maybe_icecream(24), None); + assert_eq!(maybe_icecream(25), None); + } } diff --git a/solutions/12_options/options2.rs b/solutions/12_options/options2.rs index dcf2377..0f24665 100644 --- a/solutions/12_options/options2.rs +++ b/solutions/12_options/options2.rs @@ -1,4 +1,37 @@ fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + #[test] + fn simple_option() { + let target = "rustlings"; + let optional_target = Some(target); + + // if-let + if let Some(word) = optional_target { + assert_eq!(word, target); + } + } + + #[test] + fn layered_option() { + let range = 10; + let mut optional_integers: Vec> = vec![None]; + + for i in 1..=range { + optional_integers.push(Some(i)); + } + + let mut cursor = range; + + // while-let with nested pattern matching + while let Some(Some(integer)) = optional_integers.pop() { + assert_eq!(integer, cursor); + cursor -= 1; + } + + assert_eq!(cursor, 0); + } } diff --git a/solutions/12_options/options3.rs b/solutions/12_options/options3.rs index dcf2377..c918f71 100644 --- a/solutions/12_options/options3.rs +++ b/solutions/12_options/options3.rs @@ -1,4 +1,27 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +#[derive(Debug)] +struct Point { + x: i32, + y: i32, +} + +fn main() { + let optional_point = Some(Point { x: 100, y: 200 }); + + // Solution 1: Matching over the `Option` (not `&Option`) but without moving + // out of the `Some` variant. + match optional_point { + Some(ref p) => println!("Coordinates are {},{}", p.x, p.y), + // ^^^ added + _ => panic!("No match!"), + } + + // Solution 2: Matching over a reference (`&Option`) by added `&` before + // `optional_point`. + match &optional_point { + //^ added + Some(p) => println!("Coordinates are {},{}", p.x, p.y), + _ => panic!("No match!"), + } + + println!("{optional_point:?}"); } diff --git a/solutions/13_error_handling/errors1.rs b/solutions/13_error_handling/errors1.rs index dcf2377..f552ca7 100644 --- a/solutions/13_error_handling/errors1.rs +++ b/solutions/13_error_handling/errors1.rs @@ -1,4 +1,37 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +fn generate_nametag_text(name: String) -> Result { + // ^^^^^^ ^^^^^^ + if name.is_empty() { + // `Err(String)` instead of `None`. + Err("Empty names aren't allowed".to_string()) + } else { + // `Ok` instead of `Some`. + Ok(format!("Hi! My name is {name}")) + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generates_nametag_text_for_a_nonempty_name() { + assert_eq!( + generate_nametag_text("Beyoncé".to_string()).as_deref(), + Ok("Hi! My name is Beyoncé"), + ); + } + + #[test] + fn explains_why_generating_nametag_text_fails() { + assert_eq!( + generate_nametag_text(String::new()) + .as_ref() + .map_err(|e| e.as_str()), + Err("Empty names aren't allowed"), + ); + } } diff --git a/solutions/13_error_handling/errors2.rs b/solutions/13_error_handling/errors2.rs index dcf2377..f0e144e 100644 --- a/solutions/13_error_handling/errors2.rs +++ b/solutions/13_error_handling/errors2.rs @@ -1,4 +1,58 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Say we're writing a game where you can buy items with tokens. All items cost +// 5 tokens, and whenever you purchase items there is a processing fee of 1 +// token. A player of the game will type in how many items they want to buy, and +// the `total_cost` function will calculate the total cost of the items. Since +// the player typed in the quantity, we get it as a string. They might have +// typed anything, not just numbers! +// +// Right now, this function isn't handling the error case at all. What we want +// to do is: If we call the `total_cost` function on a string that is not a +// number, that function will return a `ParseIntError`. In that case, we want to +// immediately return that error from our function and not try to multiply and +// add. +// +// There are at least two ways to implement this that are both correct. But one +// is a lot shorter! + +use std::num::ParseIntError; + +#[allow(unused_variables, clippy::question_mark)] +fn total_cost(item_quantity: &str) -> Result { + let processing_fee = 1; + let cost_per_item = 5; + + // Added `?` to propagate the error. + let qty = item_quantity.parse::()?; + // ^ added + + // Equivalent to this verbose version: + let qty = match item_quantity.parse::() { + Ok(v) => v, + Err(e) => return Err(e), + }; + + Ok(qty * cost_per_item + processing_fee) +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + use std::num::IntErrorKind; + + #[test] + fn item_quantity_is_a_valid_number() { + assert_eq!(total_cost("34"), Ok(171)); + } + + #[test] + fn item_quantity_is_an_invalid_number() { + assert_eq!( + total_cost("beep boop").unwrap_err().kind(), + &IntErrorKind::InvalidDigit, + ); + } } diff --git a/solutions/13_error_handling/errors3.rs b/solutions/13_error_handling/errors3.rs index dcf2377..63f4aba 100644 --- a/solutions/13_error_handling/errors3.rs +++ b/solutions/13_error_handling/errors3.rs @@ -1,4 +1,32 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// This is a program that is trying to use a completed version of the +// `total_cost` function from the previous exercise. It's not working though! +// Why not? What should we do to fix it? + +use std::num::ParseIntError; + +// Don't change this function. +fn total_cost(item_quantity: &str) -> Result { + let processing_fee = 1; + let cost_per_item = 5; + let qty = item_quantity.parse::()?; + + Ok(qty * cost_per_item + processing_fee) +} + +fn main() -> Result<(), ParseIntError> { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ added + let mut tokens = 100; + let pretend_user_input = "8"; + + let cost = total_cost(pretend_user_input)?; + + if cost > tokens { + println!("You can't afford that many!"); + } else { + tokens -= cost; + println!("You now have {tokens} tokens."); + } + + // Added this line to return the `Ok` variant of the expected `Result`. + Ok(()) } diff --git a/solutions/13_error_handling/errors4.rs b/solutions/13_error_handling/errors4.rs index dcf2377..70c5f1c 100644 --- a/solutions/13_error_handling/errors4.rs +++ b/solutions/13_error_handling/errors4.rs @@ -1,4 +1,42 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +use std::cmp::Ordering; + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + match value.cmp(&0) { + Ordering::Less => Err(CreationError::Negative), + Ordering::Equal => Err(CreationError::Zero), + Ordering::Greater => Ok(Self(value as u64)), + } + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_creation() { + assert_eq!( + PositiveNonzeroInteger::new(10), + Ok(PositiveNonzeroInteger(10)), + ); + assert_eq!( + PositiveNonzeroInteger::new(-10), + Err(CreationError::Negative), + ); + assert_eq!(PositiveNonzeroInteger::new(0), Err(CreationError::Zero)); + } } diff --git a/solutions/13_error_handling/errors5.rs b/solutions/13_error_handling/errors5.rs index dcf2377..c1424ee 100644 --- a/solutions/13_error_handling/errors5.rs +++ b/solutions/13_error_handling/errors5.rs @@ -1,4 +1,54 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// This exercise is an altered version of the `errors4` exercise. It uses some +// concepts that we won't get to until later in the course, like `Box` and the +// `From` trait. It's not important to understand them in detail right now, but +// you can read ahead if you like. For now, think of the `Box` type as +// an "I want anything that does ???" type. +// +// In short, this particular use case for boxes is for when you want to own a +// value and you care only that it is a type which implements a particular +// trait. To do so, The `Box` is declared as of type `Box` where +// `Trait` is the trait the compiler looks for on any value used in that +// context. For this exercise, that context is the potential errors which +// can be returned in a `Result`. + +use std::error::Error; +use std::fmt; + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +// This is required so that `CreationError` can implement `Error`. +impl fmt::Display for CreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let description = match *self { + CreationError::Negative => "number is negative", + CreationError::Zero => "number is zero", + }; + f.write_str(description) + } +} + +impl Error for CreationError {} + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + match value { + x if x < 0 => Err(CreationError::Negative), + 0 => Err(CreationError::Zero), + x => Ok(PositiveNonzeroInteger(x as u64)), + } + } +} + +fn main() -> Result<(), Box> { + let pretend_user_input = "42"; + let x: i64 = pretend_user_input.parse()?; + println!("output={:?}", PositiveNonzeroInteger::new(x)?); + Ok(()) } diff --git a/solutions/13_error_handling/errors6.rs b/solutions/13_error_handling/errors6.rs index dcf2377..ce18073 100644 --- a/solutions/13_error_handling/errors6.rs +++ b/solutions/13_error_handling/errors6.rs @@ -1,4 +1,106 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// Using catch-all error types like `Box` 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_parse_int(err: ParseIntError) -> Self { + Self::ParseInt(err) + } +} + +// As an alternative solution, implementing the `From` trait allows for the +// automatic conversion from a `ParseIntError` into a `ParsePosNonzeroError` +// using the `?` operator, without the need to call `map_err`. +// +// ``` +// let x: i64 = s.parse()?; +// ``` +// +// Traits like `From` will be dealt with in later exercises. +impl From for ParsePosNonzeroError { + fn from(err: ParseIntError) -> Self { + ParsePosNonzeroError::ParseInt(err) + } +} + +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + match value { + x if x < 0 => Err(CreationError::Negative), + 0 => Err(CreationError::Zero), + x => Ok(Self(x as u64)), + } + } + + fn parse(s: &str) -> Result { + // Return an appropriate error instead of panicking when `parse()` + // returns an error. + let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parse_int)?; + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 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 index dcf2377..e2195fd 100644 --- a/solutions/14_generics/generics1.rs +++ b/solutions/14_generics/generics1.rs @@ -1,4 +1,17 @@ +// `Vec` 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() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. + // `u8` and `i8` can both be converted to `i16`. + let mut numbers: Vec = 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 index dcf2377..14f3f7a 100644 --- a/solutions/14_generics/generics2.rs +++ b/solutions/14_generics/generics2.rs @@ -1,4 +1,28 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +struct Wrapper { + value: T, +} + +impl Wrapper { + fn new(value: T) -> Self { + Wrapper { value } + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn store_u32_in_wrapper() { + assert_eq!(Wrapper::new(42).value, 42); + } + + #[test] + fn store_str_in_wrapper() { + assert_eq!(Wrapper::new("Foo").value, "Foo"); + } } diff --git a/solutions/15_traits/traits1.rs b/solutions/15_traits/traits1.rs index dcf2377..790873f 100644 --- a/solutions/15_traits/traits1.rs +++ b/solutions/15_traits/traits1.rs @@ -1,4 +1,32 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +// 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 index dcf2377..0db93e0 100644 --- a/solutions/15_traits/traits2.rs +++ b/solutions/15_traits/traits2.rs @@ -1,4 +1,27 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +trait AppendBar { + fn append_bar(self) -> Self; +} + +impl AppendBar for Vec { + fn append_bar(mut self) -> Self { + // ^^^ this is important + self.push(String::from("Bar")); + self + } +} + +fn main() { + // You can optionally experiment here. +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_vec_pop_eq_bar() { + let mut foo = vec![String::from("Foo")].append_bar(); + assert_eq!(foo.pop().unwrap(), "Bar"); + assert_eq!(foo.pop().unwrap(), "Foo"); + } } diff --git a/solutions/15_traits/traits3.rs b/solutions/15_traits/traits3.rs index dcf2377..3d8ec85 100644 --- a/solutions/15_traits/traits3.rs +++ b/solutions/15_traits/traits3.rs @@ -1,4 +1,36 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +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 index dcf2377..3675b8d 100644 --- a/solutions/15_traits/traits4.rs +++ b/solutions/15_traits/traits4.rs @@ -1,4 +1,35 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +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 index dcf2377..1fb426a 100644 --- a/solutions/15_traits/traits5.rs +++ b/solutions/15_traits/traits5.rs @@ -1,4 +1,39 @@ -fn main() { - // DON'T EDIT THIS SOLUTION FILE! - // It will be automatically filled after you finish the exercise. +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)); + } }