08 Rustlings Structs Solution

From the ReadMe: Rust has three struct types: a classic C struct, a tuple struct, and a unit struct.

Further information

Structs in Rust

In Rust, structs are versatile containers that bring together multiple values into a cohesive unit. They offer three types: classic C structs, tuple structs, and unit structs.

Classic C structs allow you to define named fields, providing an intuitive way to organize and access related data. Tuple structs, on the other hand, resemble tuples, with fields accessed through indexing rather than names. Lastly, unit structs serve as simple markers or placeholders without any fields.

Let's take a quick dive into the types of structs:

  • Classic C Structs: These are essentially collections with named fields. They are ideal for larger and more complex data structures where each piece of data has a meaningful label.

  • Tuple Structs: These are a kind of middle ground between tuples and classic structs. They are useful when you want to give a tuple a name for type checking or to better signify its purpose, but you don't necessarily need to name each field.

  • Unit Structs: These are used in cases where a struct doesn't need to have any data associated with it. For example, they can be used to implement traits on some type, or as markers or flags.

With structs, you have the power to create custom data structures that suit your needs, enhancing code organization and efficiency. Explore the provided links to delve deeper into the world of structs in Rust and unlock their potential for your projects.

Let's look at our first exercise.

Structs1.rs

// structs1.rs
    // Address all the TODOs to make the tests pass!
    // Execute `rustlings hint structs1` or use the `hint` watch subcommand for a hint.

    // I AM NOT DONE

    struct ColorClassicStruct {
        // TODO: Something goes here
    }

    struct ColorTupleStruct(/* TODO: Something goes here */);

    #[derive(Debug)]
    struct UnitLikeStruct;

    #[cfg(test)]
    mod tests {
        use super::*;

        #[test]
        fn classic_c_structs() {
            // TODO: Instantiate a classic c struct!
            // let green =

            assert_eq!(green.red, 0);
            assert_eq!(green.green, 255);
            assert_eq!(green.blue, 0);
        }

        #[test]
        fn tuple_structs() {
            // TODO: Instantiate a tuple struct!
            // let green =

            assert_eq!(green.0, 0);
            assert_eq!(green.1, 255);
            assert_eq!(green.2, 0);
        }

        #[test]
        fn unit_structs() {
            // TODO: Instantiate a unit-like struct!
            // let unit_like_struct =
            let message = format!("{:?}s are fun!", unit_like_struct);

            assert_eq!(message, "UnitLikeStructs are fun!");
        }
    }

So here we're getting clear instructions on what to do. We need to implement different types of structs in each //TODO section. For completeness here are our errors.

Structs1.rs errors

⚠️  Compiling of exercises/structs/structs1.rs failed! Please try again. Here is the output:
error[E0425]: cannot find value `green` in this scope
  --> exercises/structs/structs1.rs:25:20
   |
25 |         assert_eq!(green.red, 0);
   |                    ^^^^^ not found in this scope

error[E0425]: cannot find value `green` in this scope
  --> exercises/structs/structs1.rs:26:20
   |
26 |         assert_eq!(green.green, 255);
   |                    ^^^^^ not found in this scope

error[E0425]: cannot find value `green` in this scope
  --> exercises/structs/structs1.rs:27:20
   |
27 |         assert_eq!(green.blue, 0);
   |                    ^^^^^ not found in this scope

error[E0425]: cannot find value `green` in this scope
  --> exercises/structs/structs1.rs:35:20
   |
35 |         assert_eq!(green.0, 0);
   |                    ^^^^^ not found in this scope

error[E0425]: cannot find value `green` in this scope
  --> exercises/structs/structs1.rs:36:20
   |
36 |         assert_eq!(green.1, 255);
   |                    ^^^^^ not found in this scope

error[E0425]: cannot find value `green` in this scope
  --> exercises/structs/structs1.rs:37:20
   |
37 |         assert_eq!(green.2, 0);
   |                    ^^^^^ not found in this scope

error[E0425]: cannot find value `unit_like_struct` in this scope
  --> exercises/structs/structs1.rs:44:49
   |
14 | struct UnitLikeStruct;
   | ---------------------- similarly named unit struct `UnitLikeStruct` defined here
...
44 |         let message = format!("{:?}s are fun!", unit_like_struct);
   |                                                 ^^^^^^^^^^^^^^^^ help: a unit struct with a similar name exists: `UnitLikeStruct`

warning: unused import: `super::*`
  --> exercises/structs/structs1.rs:18:9
   |
18 |     use super::*;
   |         ^^^^^^^^
   |
   = note: `#[warn(unused_imports)]` on by default

error: aborting due to 7 previous errors; 1 warning emitted

We're getting 7 different errors and it looks like most of them are related to the fact that we are missing our structs so they assert_eq! calls are failing. So let's try and solve this.

Notice that the errors reported here are not solely due to missing structs but also due to missing variable declarations. For example, the variables green and unit_like_struct are used in the test assertions but haven't been defined yet in the tests. This will be addressed in the following sections as we instantiate these variables along with defining and instantiating the corresponding structs.

Defining our Structs

Starting from the top of our code we see our first TODO's in comments.

   struct ColorClassicStruct {
        // TODO: Something goes here
    }

    struct ColorTupleStruct(/* TODO: Something goes here */);

We have 2 different styles of structs that we need to create a "classic" and "tuple" types struct. We fill out the structs using their usual structure and we get an indication of what is needed by looking at the assert_eq! listed in each section. We can see that in the assert_eq! we have values that could fit and i32's since we see the max value is 255 we'll use i32s as it's the default value in Rust.

struct ColorClassicStruct {
    red: i32,
    green: i32,
    blue: i32,
}
// from the `assert_eq!` in `classic_c_structs` function
    // assert_eq!(green.red, 0);
    // assert_eq!(green.green, 255);
    // assert_eq!(green.blue, 0);


struct ColorTupleStruct(i32, i32, i32);

// from the `assert_eq!` in the `tuple_structs` function
    // assert_eq!(green.0, 0);
    // assert_eq!(green.1, 255);
    // assert_eq!(green.2, 0);

We'll do the same for the ColorTupleStruct getting hints as to how we need to define the Tuple struct with (i32, i32, i32).

Instantiating our Classic Struct

Now that we've defined our structs we need to create an new instance of our structs where we specify concrete values for each of the fields. We create instances of our struct by stating the name of the struct we can see that in our example we have this so far.

   fn classic_c_structs() {
            // TODO: Instantiate a classic c struct!
            // let green =

            assert_eq!(green.red, 0);
            assert_eq!(green.green, 255);
            assert_eq!(green.blue, 0);
        }

So we'll start by removing the comment slashes on let green = and fill out the rest with our values, again taking hints from the assert_eq! lines we see that we should have values of 0, 255, 0 so let's add that information into our classic_c_structs() function. It should like the code below:

    fn classic_c_structs() {
        // TODO: Instantiate a classic c struct!
        let green = ColorClassicStruct {
            red: 0,
            green: 255,
            blue: 0,
        };

Instantiating our Tuple Struct

We see a similar set up for our Tuple struct with the code below. We have our let gree = commented out with a hint as to what the values should be by looking at our assert_eq! macros

        fn tuple_structs() {
            // TODO: Instantiate a tuple struct!
            // let green =

            assert_eq!(green.0, 0);
            assert_eq!(green.1, 255);
            assert_eq!(green.2, 0);
        }

Remembering that Tuple structs have a different type of structure as we created struct ColorTupleStruct(i32, i32, i32), we can instantiate our tuple struct in the following way:

fn tuple_structs() {
        // TODO: Instantiate a tuple struct!
        let green = ColorTupleStruct(0, 255, 0);

        assert_eq!(green.0, 0);
        assert_eq!(green.1, 255);
        assert_eq!(green.2, 0);

Again taking hints from the values that we see in the assert_eq! macro's

Instantiating our Unit-Like Structs

We have our final struct left to instantiate which if we recall from reading the The Rust Programming Language book, they are structs that have any fields. So looking at our unfinished code we see this:

        fn unit_structs() {
            // TODO: Instantiate a unit-like struct!
            // let unit_like_struct =
            let message = format!("{:?}s are fun!", unit_like_struct);

            assert_eq!(message, "UnitLikeStructs are fun!");

So, as we don't have any fields and hence no data stored in them we can simply finish our code like so:

fn unit_structs() {
        // TODO: Instantiate a unit-like struct!
        let unit_like_struct = UnitLikeStruct;
        let message = format!("{:?}s are fun!", unit_like_struct);

        assert_eq!(message, "UnitLikeStructs are fun!");
    }

That should do the trick, let's see if our code complies. It does!

Structs1.rs Solution


struct ColorClassicStruct {
    red: i32,
    green: i32,
    blue: i32,
}

struct ColorTupleStruct(i32, i32, i32);

#[derive(Debug)]
struct UnitLikeStruct;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn classic_c_structs() {
        // TODO: Instantiate a classic c struct!
        let green = ColorClassicStruct {
            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() {
        // TODO: Instantiate a tuple struct!
        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() {
        // TODO: Instantiate a unit-like struct!
        let unit_like_struct = UnitLikeStruct;
        let message = format!("{:?}s are fun!", unit_like_struct);

        assert_eq!(message, "UnitLikeStructs are fun!");
    }
}

Our code compiles and we've finished and instantiated 3 different types of structs, our printout confirms this:

✅ Successfully tested exercises/structs/structs1.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

On to the next one!

structs2.rs

// structs2.rs
// Address all the TODOs to make the tests pass!
// Execute `rustlings hint structs2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

#[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,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn your_order() {
        let order_template = create_order_template();
        // TODO: Create your own order using the update syntax and template above!
        // let your_order =
        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);
    }
}

In this code example we have instructions to address all the //TODO's which we can see that we only have one at the bottom of our code in our tests. As always let's look at our errors.

structs2.rs errors

⚠️  Compiling of exercises/structs/structs2.rs failed! Please try again. Here is the output:
error[E0609]: no field `name` on type `fn() {tests::your_order}`
  --> exercises/structs/structs2.rs:39:31
   |
39 |         assert_eq!(your_order.name, "Hacker in Rust");
   |                               ^^^^

error[E0609]: no field `year` on type `fn() {tests::your_order}`
  --> exercises/structs/structs2.rs:40:31
   |
40 |         assert_eq!(your_order.year, order_template.year);
   |                               ^^^^

error[E0609]: no field `made_by_phone` on type `fn() {tests::your_order}`
  --> exercises/structs/structs2.rs:41:31
   |
41 |         assert_eq!(your_order.made_by_phone, order_template.made_by_phone);
   |                               ^^^^^^^^^^^^^

error[E0609]: no field `made_by_mobile` on type `fn() {tests::your_order}`
  --> exercises/structs/structs2.rs:42:31
   |
42 |         assert_eq!(your_order.made_by_mobile, order_template.made_by_mobile);
   |                               ^^^^^^^^^^^^^^

error[E0609]: no field `made_by_email` on type `fn() {tests::your_order}`
  --> exercises/structs/structs2.rs:43:31
   |
43 |         assert_eq!(your_order.made_by_email, order_template.made_by_email);
   |                               ^^^^^^^^^^^^^

error[E0609]: no field `item_number` on type `fn() {tests::your_order}`
  --> exercises/structs/structs2.rs:44:31
   |
44 |         assert_eq!(your_order.item_number, order_template.item_number);
   |                               ^^^^^^^^^^^

error[E0609]: no field `count` on type `fn() {tests::your_order}`
  --> exercises/structs/structs2.rs:45:31
   |
45 |         assert_eq!(your_order.count, 1);
   |                               ^^^^^

error: aborting due to 7 previous errors

We have a total of 7 errors all created by our missing struct instantiation. So, let's get it to it, we'll start from the top.

Instantiating your_order

If we look at our first error we get a major hint as to what the compiler expects to see: assert_eq!(your_order.name, "Hacker in Rust") so if we take a look at our sample code and build our Order in the same way as in the create_order_template function we should have a great start for what we need to do.

If we use our Order struct as a guide we can start entering the values for each field.

struct Order {
    name: String,
    year: u32,
    made_by_phone: bool,
    made_by_mobile: bool,
    made_by_email: bool,
    item_number: u32,
    count: u32,
}

for example:

fn your_order() {
        let order_template = create_order_template();
        // TODO: Create your own order using the update syntax and template above!
        let your_order = Order {
            name: String::from("Hacker in Rust"),
            year: u32,
            made_by_phone: bool,
            made_by_mobile: bool,
            made_by_email: bool,
            item_number: u32,
            count: u32,
        };

Again we see what is expected by looking at our assert_eq! macro, so following this format it should be pretty easy to understand what each field should be.

        assert_eq!(your_order.name, "Hacker in Rust"); // String
        assert_eq!(your_order.year, order_template.year); // u32 from the `oder_template.year`
        assert_eq!(your_order.made_by_phone, order_template.made_by_phone); // bool
        assert_eq!(your_order.made_by_mobile, order_template.made_by_mobile); // bool
        assert_eq!(your_order.made_by_email, order_template.made_by_email); // bool
        assert_eq!(your_order.item_number, order_template.item_number); // u32 from `item_number`
        assert_eq!(your_order.count, 1); // u32 defined here as `1`

So there we go now all we need to is fill out the rest of the values and it should look like this:

let your_order = Order {
            name: String::from("Hacker in Rust"),
            year: 2019,
            made_by_phone: false,
            made_by_mobile: false,
            made_by_email: true,
            item_number: 123,
            count: 1,
        };

structs2.rs Solution

#[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,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn your_order() {
        let order_template = create_order_template();
        // TODO: Create your own order using the update syntax and template above!
        let your_order = Order {
            name: String::from("Hacker in Rust"),
            year: 2019,
            made_by_phone: false,
            made_by_mobile: false,
            made_by_email: true,
            item_number: 123,
            count: 1,
        };
        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);
    }
}
✅ Successfully tested exercises/structs/structs2.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

Understanding Rust Struct Update Syntax

Before we move on to the next exercise, it's worth mentioning a Rust feature that could be beneficial here - the struct update syntax. It allows you to create a new instance of a struct, and selectively update some or all fields. This is especially useful when you have a template or default struct that you want to use as a base for your new struct.

To do so, you use .. followed by the instance name. It should look like this:

let your_order = Order {
    name: String::from("Hacker in Rust"),
    count: 1,
    ..order_template
};

This code creates a new Order that is the same as order_template, except name is "Hacker in Rust" and count is 1.

We can use this syntax to make our code more efficient and maintain the logic of using a template.

structs2.rs Solution with Update Syntax

So, with struct update syntax, the solution becomes:

#[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,
            ..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);
    }
}

The test function your_order() now uses the update syntax to create your_order from order_template, changing only the name and count fields, and leaving the rest as in order_template.

This is just an alternate way of solving the problem and could be particularly useful if Order had many more fields that remained the same between order_template and your_order.

On to structs3.rs!

structs3.rs

// structs3.rs
// 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.
// Make the code compile and the tests pass!
// Execute `rustlings hint structs3` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

#[derive(Debug)]
struct Package {
    sender_country: String,
    recipient_country: String,
    weight_in_grams: i32,
}

impl Package {
    fn new(sender_country: String, recipient_country: String, weight_in_grams: i32) -> Package {
        if weight_in_grams <= 0 {
            panic!("Can not ship a weightless package.")
        } else {
            Package {
                sender_country,
                recipient_country,
                weight_in_grams,
            }
        }
    }

    fn is_international(&self) -> ??? {
        // Something goes here...
    }

    fn get_fees(&self, cents_per_gram: i32) -> ??? {
        // Something goes 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, -2210);
    }

    #[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);
    }
}

Alright this is a longer piece of code to go through but we see that there is essentially 2 areas that we need to fill out, before we look at that let's look at the errors.

⚠️  Compiling of exercises/structs/structs3.rs failed! Please try again. Here is the output:
error: expected identifier, found `{`
  --> exercises/structs/structs3.rs:29:39
   |
16 | impl Package {
   |              - while parsing this item list starting here
...
29 |     fn is_international(&self) -> ??? {
   |                                       ^ expected identifier
...
36 | }
   | - the item list ends here

error[E0599]: no method named `is_international` found for struct `Package` in the current scope
  --> exercises/structs/structs3.rs:58:25
   |
10 | struct Package {
   | -------------- method `is_international` not found for this struct
...
58 |         assert!(package.is_international());
   |                         ^^^^^^^^^^^^^^^^ method not found in `Package`

error[E0599]: no method named `is_international` found for struct `Package` in the current scope
  --> exercises/structs/structs3.rs:68:26
   |
10 | struct Package {
   | -------------- method `is_international` not found for this struct
...
68 |         assert!(!package.is_international());
   |                          ^^^^^^^^^^^^^^^^ method not found in `Package`

error[E0599]: no method named `get_fees` found for struct `Package` in the current scope
  --> exercises/structs/structs3.rs:80:28
   |
10 | struct Package {
   | -------------- method `get_fees` not found for this struct
...
80 |         assert_eq!(package.get_fees(cents_per_gram), 4500);
   |                            ^^^^^^^^ method not found in `Package`

error[E0599]: no method named `get_fees` found for struct `Package` in the current scope
  --> exercises/structs/structs3.rs:81:28
   |
10 | struct Package {
   | -------------- method `get_fees` not found for this struct
...
81 |         assert_eq!(package.get_fees(cents_per_gram * 2), 9000);
   |                            ^^^^^^^^ method not found in `Package`

error: aborting due to 5 previous errors

Alright we're seeing errors due to the missing code blocks and also the usual assert_eq! macros that are expecting something that is not there. So let's get to work!

Defining Return Values

First we define the return values String and an i32, instead of the ??? we previously had.

    fn is_international(&self) -> String {
        // Something goes here...
    }

    fn get_fees(&self, cents_per_gram: i32) -> i32 {
        // Something goes here...
    }
}

We still get error's here when we save but they're different essentially telling us that this function has no tail or essentially isn't returning anything as we have defined it in the signature so of course we have to fix that. Here are the new errors:

⚠️  Compiling of exercises/structs/structs3.rs failed! Please try again. Here is the output:
error[E0308]: mismatched types
  --> exercises/structs/structs3.rs:29:35
   |
29 |     fn is_international(&self) -> String {
   |        ----------------           ^^^^^^ expected struct `String`, found `()`
   |        |
   |        implicitly returns `()` as its body has no tail or `return` expression

error[E0308]: mismatched types
  --> exercises/structs/structs3.rs:33:48
   |
33 |     fn get_fees(&self, cents_per_gram: i32) -> i32 {
   |        --------                                ^^^ expected `i32`, found `()`
   |        |
   |        implicitly returns `()` as its body has no tail or `return` expression
   |
help: consider returning the local binding `cents_per_gram`

is_international function

So let's take breakdown the is_interational function and see what it is that we need to do inside of here.

Function declaration:

The line fn is_international(&self) -> bool declares a function named is_international. The &self argument means that this function is a method, which operates on an instance of the Package struct.

This function will return a bool type, which is a boolean that can be either true or false as we have already defined earlier.

Function body:

Inside the function, we add self.sender_country != self.recipient_country which is the basic logic that determines whether a package is international.

self.sender_country accesses the sender_country field of the current Package instance, and self.recipient_country accesses the recipient_country field.

!= is the inequality operator in Rust, which checks if the two values on its left and right are not equal. In this case, it's comparing the sender_country and recipient_country.

Return value:

If sender_country and recipient_country are not equal, the expression evaluates to true, meaning that the package is international. If they are equal, the expression evaluates to false, meaning that the package is not international.

This boolean value is what the function returns, because it's the last (and in this case, the only) expression in the function. In Rust, the last expression in a function is implicitly returned.

In sum, is_international is a method that checks if the sender and recipient countries of a package are different. If they are, the package is international, and the method returns true. If they're the same, the package is not international, and the method returns false.

get_fees function

Now let's break down the get_fees function.

Function declaration

The line fn get_fees(&self, cents_per_gram: i32) -> i32 declares a function named get_fees. The &self argument means that this function is a method, which operates on an instance of the Package struct.

In addition to &self, the function takes another argument, cents_per_gram, which represents the cost per gram to ship a package. The -> i32 means this function will return an integer (i32).

Function body:

Inside the function, cents_per_gram * self.weight_in_grams is the logic used to calculate the shipping fee for a package. self.weight_in_grams accesses the weight_in_grams field of the current Package instance.

* is the multiplication operator in Rust, which multiplies cents_per_gram (the cost per gram to ship a package) with self.weight_in_grams (the weight of the package).

Return Value

The multiplication gives the total cost to ship the package. Since cents_per_gram is in cents, and weight_in_grams is in grams, the result is in cents too. This integer value is what the function returns. As in the is_international function, the last expression in the function is implicitly returned in Rust.

In summary, get_fees is a method that calculates and returns the shipping fee for a package based on the cost per gram and the weight of the package.

Summary of methods

Rust is a language that uses something called "methods" to perform actions on data. In our example, Package is a collection of data (the sender and recipient countries, and the weight), and the functions is_international and get_fees are methods that do something with this data.

  1. &self is a reference to the current object - the "package" we're working with at the moment. It's like saying "this package" or "the package I'm currently handling". It's used because the method is working on the data inside the current Package object.

  2. is_international(&self) -> bool is a method that checks if the package is international. It does this by comparing the sender and recipient countries: if they're different, it means the package is international, and the method returns true. If they're the same, the package is not international, and the method returns false.

  3. get_fees(&self, cents_per_gram: i32) -> i32 is a method that calculates the fees to ship the package. It does this by multiplying the weight of the package (self.weight_in_grams) by the cost per gram (cents_per_gram). The result is the total fee, which is returned by the method.

So, in short: &self means "this package", is_international checks if the sender and recipient countries are different, and get_fees calculates the shipping fees based on the weight of the package and the cost per gram.

Conclusion

Okay, this was a long one, we dove into the basics of Structs in Rust and how they can carry both data and logic. Through this exploration, we gained a better understanding of Rust's data structures, method definitions and syntax. Moreover, we tackled a practical example with tests, giving us a real world scenario to apply our knowledge.

Remember, Rust's system of Structs and methods allows you to write code that is not only efficient but also safe and readable. This is particularly important in systems programming and other contexts where performance is crucial. With this knowledge, you'll be able to tackle more complex Rust projects and continue to build on your programming skills.