diff --git a/CHANGELOG.md b/CHANGELOG.md index e2acf6c6..807f7d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - `structs3`: Rewrote the exercise to make users type method syntax themselves. - Rename the exercises for smart pointers and conversions so they're sorted alphabetically. [@foxfromworld](https://github.com/foxfromworld) - `vecs1`: Remove array literal. Some learners assumed their task is to convert it to a vector. +- `conversions2`: Redesign the context such that infallible conversion makes sense. ## 6.5.0 (2025-08-21) diff --git a/exercises/23_conversions/conversions2.rs b/exercises/23_conversions/conversions2.rs index bc2783a3..f6df67f2 100644 --- a/exercises/23_conversions/conversions2.rs +++ b/exercises/23_conversions/conversions2.rs @@ -2,49 +2,36 @@ // 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 +// +// Frank the fairy would like to buy some truffles from Grace the gnome, a +// world-renowned chocolatier. The truffles are priced in GnomeCoin though, and +// Frank only has FairyCredit. Help Frank by providing a `From` implementation +// to convert his FairyCredit to GnomeCoin. At the current exchange rate, one +// FairyCredit is valued at 100 GnomeCoin. #[derive(Debug)] -struct Person { - name: String, - age: u8, +struct FairyCredit(u32); + +#[derive(Debug, PartialEq)] +struct GnomeCoin(u64); + +impl From for GnomeCoin { + // TODO: implement From for GnomeCoin } -// 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, - } - } -} - -// TODO: Complete this `From` implementation to be able to parse a `Person` -// out of a string in the form of "Mark,20". -// Note that you'll need to parse the age component into a `u8` with something -// like `"4".parse::()`. -// -// Steps: -// 1. Split the given string on the commas present in it. -// 2. If the split operation returns less or more than 2 elements, return the -// default of `Person`. -// 3. Use the first element from the split operation as the name. -// 4. If the name is empty, return the default of `Person`. -// 5. Parse the second element from the split operation into a `u8` as the age. -// 6. If parsing the age fails, return the default of `Person`. -impl From<&str> for Person { - fn from(s: &str) -> Self {} -} +// Note that we shouldn't provide the opposite conversion: from GnomeCoin to +// FairyCredits. That's because less than 100 GnomeCoins cannot be represented +// as FairyCredits, which would make the conversion lossy. The `From` trait is +// only appropriate for infallible and lossless conversions. fn main() { // Use the `from` function. - let p1 = Person::from("Mark,20"); - println!("{p1:?}"); + let g1 = GnomeCoin::from(FairyCredit(12)); + println!("{g1:?}"); - // Since `From` is implemented for Person, we are able to use `Into`. - let p2: Person = "Gerald,70".into(); - println!("{p2:?}"); + // Since `From` is implemented for GnomeCoin, we are able to use `Into`. + let g2: GnomeCoin = FairyCredit(9).into(); + println!("{g2:?}"); } #[cfg(test)] @@ -52,79 +39,14 @@ mod tests { use super::*; #[test] - fn test_default() { - let dp = Person::default(); - assert_eq!(dp.name, "John"); - assert_eq!(dp.age, 30); + fn test_from() { + let g = GnomeCoin::from(FairyCredit(12)); + assert_eq!(g, GnomeCoin(1200)); } #[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); + fn test_into() { + let g: GnomeCoin = FairyCredit(9).into(); + assert_eq!(g, GnomeCoin(900)); } } diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 637283d2..054da99b 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -1171,7 +1171,9 @@ Use the `as` operator to cast one of the operands in the last line of the name = "conversions2" dir = "23_conversions" hint = """ -Follow the steps provided right before the `From` implementation.""" +Implement From for GnomeCoin. Check the documentation of `From` to +learn about its required items: +https://doc.rust-lang.org/std/convert/trait.From.html""" [[exercises]] name = "conversions3" diff --git a/solutions/23_conversions/conversions2.rs b/solutions/23_conversions/conversions2.rs index cec23cb4..b8b2372e 100644 --- a/solutions/23_conversions/conversions2.rs +++ b/solutions/23_conversions/conversions2.rs @@ -2,55 +2,38 @@ // 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 +// +// Frank the fairy would like to buy some truffles from Grace the gnome, a +// world-renowned chocolatier. The truffles are priced in GnomeCoin though, and +// Frank only has FairyCredit. Help Frank by providing a `From` implementation +// to convert his FairyCredit to GnomeCoin. At the current exchange rate, one +// FairyCredit is valued at 100 GnomeCoin. #[derive(Debug)] -struct Person { - name: String, - age: u8, -} +struct FairyCredit(u32); -// 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, - } +#[derive(Debug, PartialEq)] +struct GnomeCoin(u64); + +impl From for GnomeCoin { + fn from(value: FairyCredit) -> Self { + Self(value.0 as u64 * 100) } } -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, - } - } -} +// Note that we shouldn't provide the opposite conversion: from GnomeCoin to +// FairyCredits. That's because less than 100 GnomeCoins cannot be represented +// as FairyCredits, which would make the conversion lossy. The `From` trait is +// only appropriate for infallible and lossless conversions. fn main() { // Use the `from` function. - let p1 = Person::from("Mark,20"); - println!("{p1:?}"); + let g1 = GnomeCoin::from(FairyCredit(12)); + println!("{g1:?}"); - // Since `From` is implemented for Person, we are able to use `Into`. - let p2: Person = "Gerald,70".into(); - println!("{p2:?}"); + // Since `From` is implemented for GnomeCoin, we are able to use `Into`. + let g2: GnomeCoin = FairyCredit(9).into(); + println!("{g2:?}"); } #[cfg(test)] @@ -58,79 +41,14 @@ mod tests { use super::*; #[test] - fn test_default() { - let dp = Person::default(); - assert_eq!(dp.name, "John"); - assert_eq!(dp.age, 30); + fn test_from() { + let g = GnomeCoin::from(FairyCredit(12)); + assert_eq!(g, GnomeCoin(1200)); } #[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); + fn test_into() { + let g: GnomeCoin = FairyCredit(9).into(); + assert_eq!(g, GnomeCoin(900)); } }