diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d2b4153d6..128df3b923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Changed - `vecs2`: Removed the use of `map` and `collect`, which are only taught later. +- `structs3`: Rewrote the exercise to make users type method syntax themselves. ## 6.5.0 (2025-08-21) diff --git a/exercises/07_structs/structs3.rs b/exercises/07_structs/structs3.rs index 69e5ced7f8..6d872eba15 100644 --- a/exercises/07_structs/structs3.rs +++ b/exercises/07_structs/structs3.rs @@ -1,37 +1,33 @@ // Structs contain data, but can also have logic. In this exercise, we have -// defined the `Package` struct, and we want to test some logic attached to it. +// defined the `Fireworks` struct and a couple of functions that work with it. +// Turn these free-standing functions into methods and associated functions +// to express that relationship more clearly in the code. + +#![deny(clippy::use_self)] // practice using the `Self` type #[derive(Debug)] -struct Package { - sender_country: String, - recipient_country: String, - weight_in_grams: u32, +struct Fireworks { + rockets: usize, } -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, - } - } +// TODO: Turn this function into an associated function on `Fireworks`. +fn new_fireworks() -> Fireworks { + Fireworks { rockets: 0 } +} - // TODO: Add the correct return type to the function signature. - fn is_international(&self) { - // TODO: Read the tests that use this method to find out when a package - // is considered international. - } +// TODO: Turn this function into a method on `Fireworks`. +fn add_rockets(fireworks: &mut Fireworks, rockets: usize) { + fireworks.rockets += rockets +} - // TODO: Add the correct return type to the function signature. - fn get_fees(&self, cents_per_gram: u32) { - // TODO: Calculate the package's fees. +// TODO: Turn this function into a method on `Fireworks`. +fn start(fireworks: Fireworks) -> String { + if fireworks.rockets < 5 { + String::from("small") + } else if fireworks.rockets < 20 { + String::from("medium") + } else { + String::from("big") } } @@ -44,44 +40,19 @@ 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); + fn start_some_fireworks() { + let mut f = Fireworks::new(); + f.add_rockets(3); + assert_eq!(f.start(), "small"); + + let mut f = Fireworks::new(); + f.add_rockets(15); + assert_eq!(f.start(), "medium"); + + let mut f = Fireworks::new(); + f.add_rockets(100); + assert_eq!(Fireworks::start(f), "big"); + // We don't use method syntax in the last test to ensure the `start` + // function takes ownership of the fireworks. } } diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index ca3ecf1f03..1e41383376 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -417,11 +417,10 @@ https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances- name = "structs3" dir = "07_structs" hint = """ -For `is_international`: What makes a package international? Seems related to -the places it goes through right? - -For `get_fees`: This method takes an additional argument, is there a field in -the `Package` struct that this relates to? +Methods and associated functions are both declared in an `impl MyType {}` +block. Methods have a `self`, `&self` or `&mut self` parameter, where `self` +implicitly has the type of the impl block. Associated functions do not have +a `self` parameter. Have a look in The Book to find out more about method implementations: https://doc.rust-lang.org/book/ch05-03-method-syntax.html""" diff --git a/solutions/07_structs/structs3.rs b/solutions/07_structs/structs3.rs index 3f878cc86c..74c7a71523 100644 --- a/solutions/07_structs/structs3.rs +++ b/solutions/07_structs/structs3.rs @@ -1,33 +1,27 @@ +#![deny(clippy::use_self)] // practice using the `Self` type + #[derive(Debug)] -struct Package { - sender_country: String, - recipient_country: String, - weight_in_grams: u32, +struct Fireworks { + rockets: usize, } -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, - } +impl Fireworks { + fn new() -> Self { + Self { rockets: 0 } } - fn is_international(&self) -> bool { - // ^^^^^^^ added - self.sender_country != self.recipient_country + fn add_rockets(&mut self, rockets: usize) { + self.rockets += rockets } - fn get_fees(&self, cents_per_gram: u32) -> u32 { - // ^^^^^^ added - self.weight_in_grams * cents_per_gram + fn start(self) -> String { + if self.rockets < 5 { + String::from("small") + } else if self.rockets < 20 { + String::from("medium") + } else { + String::from("big") + } } } @@ -40,44 +34,19 @@ 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); + fn start_some_fireworks() { + let mut f = Fireworks::new(); + f.add_rockets(3); + assert_eq!(f.start(), "small"); + + let mut f = Fireworks::new(); + f.add_rockets(15); + assert_eq!(f.start(), "medium"); + + let mut f = Fireworks::new(); + f.add_rockets(100); + assert_eq!(Fireworks::start(f), "big"); + // We don't use method syntax in the last test to ensure the `start` + // function takes ownership of the fireworks. } }