11 Rustlings Modules Solution

Modules

In this section we'll give you an introduction to Rust's module system.

Further information

modules1.rs

The first exercise focuses on controlling the visibility of functions within a module. The goal is to restrict access to the get_secret_recipe function outside the sausage_factory module. Let's take a look at the initial code:

// modules1.rs
// Execute `rustlings hint modules1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

mod sausage_factory {
    // Don't let anybody outside of this module see this!
    fn get_secret_recipe() -> String {
        String::from("Ginger")
    }

    fn make_sausage() {
        get_secret_recipe();
        println!("sausage!");
    }
}

fn main() {
    sausage_factory::make_sausage();
}

Our instructions in this first exercise are to not let anyone outside of the sausage_factory module see the get_secret_recipe function.

modules1.rs errors

⚠️  Compiling of exercises/modules/modules1.rs failed! Please try again. Here is the output:
error[E0603]: function `make_sausage` is private
  --> exercises/modules/modules1.rs:19:22
   |
19 |     sausage_factory::make_sausage();
   |                      ^^^^^^^^^^^^ private function
   |
note: the function `make_sausage` is defined here
  --> exercises/modules/modules1.rs:12:5
   |
12 |     fn make_sausage() {
   |     ^^^^^^^^^^^^^^^^^

error: aborting due to previous error

We see that our errors tell us that our make_sausage() function is private and we can see that we are trying to access it in our fn main() so, our fix is simple, make the function that we need to see outside of the sausage_factory module accessible by adding a pub keyword. Let's try this.

modules1.rs solution

mod sausage_factory {
    // Don't let anybody outside of this module see this!
    fn get_secret_recipe() -> String {
        String::from("Ginger")
    }

    pub fn make_sausage() { // adding `pub` here fixes our issue
        get_secret_recipe();
        println!("sausage!");
    }
}

fn main() {
    sausage_factory::make_sausage();
}

Simple solution by adding the pub keyword to our make_sausage() function we get access to it from our main() function and our code compiles and prints:

sausage!

modules2.rs

// modules2.rs
// You can bring module paths into scopes and provide new names for them with the
// 'use' and 'as' keywords. Fix these 'use' statements to make the code compile.
// Execute `rustlings hint modules2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

mod delicious_snacks {
    // TODO: Fix these use statements
	use self::fruits::{PEAR as fruit};
    use self::veggies::CUCUMBER as ???

    mod fruits {
        pub const PEAR: &'static str = "Pear";
        pub const APPLE: &'static str = "Apple";
    }

    mod veggies {
        pub const CUCUMBER: &'static str = "Cucumber";
        pub const CARROT: &'static str = "Carrot";
    }
}

fn main() {
    println!(
        "favorite snacks: {} and {}",
        delicious_snacks::fruit,
        delicious_snacks::veggie
    );
}

Our instructions here are to bring our module into scope by using use and as keywords. We can see and incomplete use statement on line 11. Let's take a quick look at the errors.

modules2.rs errors

⚠️  Compiling of exercises/modules/modules2.rs failed! Please try again. Here is the output:
error: expected identifier, found `?`
  --> exercises/modules/modules2.rs:10:31
   |
10 |     use self::fruits::PEAR as ???
   |                               ^ expected identifier

error: aborting due to previous error

The compiler confirms what we already know, but it actually gives us a big hint in telling us that it expects an identifier. So, let's add the identifier.

modules2.rs solution

By looking at our code we can understand what the code is expecting as an identifier. It specifically shows in our main() function that we need to print the delicious_snacks::fruit, and the delicious_snacks::veggie, so this is a clear indication that we must identify our cucumber "as" a veggie. We also need to add the pub keyword to make sure that they can be accessed outside of delicious_snacks.

mod delicious_snacks {
    // TODO: Fix these use statements
    pub use self::fruits::PEAR as fruit;
    pub use self::veggies::CUCUMBER as veggie; // adding veggie identifier here

    mod fruits {
        pub const PEAR: &'static str = "Pear";
        pub const APPLE: &'static str = "Apple";
    }

    mod veggies {
        pub const CUCUMBER: &'static str = "Cucumber";
        pub const CARROT: &'static str = "Carrot";
    }
}

fn main() {
    println!(
        "favorite snacks: {} and {}",
        delicious_snacks::fruit,
        delicious_snacks::veggie
    );
}

And it works, we're printing out of favorite snacks a pear and cucumber using the as keyword.

Output:
====================
favorite snacks: Pear and Cucumber

====================

modules3.rs

// modules3.rs
// You can use the 'use' keyword to bring module paths from modules from anywhere
// and especially from the Rust standard library into your scope.
// Bring SystemTime and UNIX_EPOCH
// from the std::time module. Bonus style points if you can do it with one line!
// Execute `rustlings hint modules3` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

// TODO: Complete this use statement
use ???

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!"),
    }
}

In this third exercise, we encounter code that involves the use of UNIX_EPOCH and SystemTime. Let's take a closer look at what these terms mean and how they relate to each other.

UNIX_EPOCH represents a specific point in time: the start of the Unix time system. In Unix-based operating systems, time is often measured as the number of seconds that have elapsed since January 1, 1970, at 00:00:00 UTC (Coordinated Universal Time). This point in time is commonly referred to as the Unix epoch.

On the other hand, SystemTime is a type provided by the Rust standard library that represents the system's current time or a specific point in time. It is a flexible type that can handle different platforms and allows us to perform various operations on time values.

In the code snippet provided, the match statement is used to handle the result of calculating the duration between the current time (SystemTime::now()) and the Unix epoch (UNIX_EPOCH). By subtracting the Unix epoch from the current time, we can determine the duration that has passed since the Unix epoch.

If the calculation is successful (Ok(n)), we print the number of seconds that have elapsed since the Unix epoch. If an error occurs (Err(_)), indicating that the system time is before the Unix epoch, we panic with an appropriate error message.

By utilizing UNIX_EPOCH and SystemTime, we can work with time-related operations in Rust and perform calculations based on the Unix time system. Understanding these concepts enhances our ability to handle and manipulate time data effectively.

Now that we have a better understanding of UNIX_EPOCH and SystemTime, let's proceed with the code solution to complete the exercise.

modules3.rs errors

When analyzing the error messages in the modules3.rs exercise, we can see that they don't provide any additional insights beyond the fact that an import statement is expected after the use keyword. The error message explicitly states that an identifier is anticipated after the use keyword. Therefore, to resolve this issue, we need to provide a valid identifier in our import statement.

⚠️  Compiling of exercises/modules/modules3.rs failed! Please try again. Here is the output:
error: expected identifier, found `?`
  --> exercises/modules/modules3.rs:11:5
   |
11 | use ???
   |     ^ expected identifier

error: aborting due to previous error

modules3.rs solution

Our hints are pretty direct we need to import from the std::time module both UNIX_EPOCH and SystemTime into our code, and in order to save space and time we should it in one line. The way we do this is by including adding curly braces{} into our import statement and separating each item with a comma, easy enough.

use std::time::{SystemTime, UNIX_EPOCH};

fn main() {
    match SystemTime::now().duration_since(UNIX_EPOCH) {
        Ok(n) => println!("19-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()),
        Err(_) => panic!("SystemTime before UNIX EPOCH!"),
    }
}

This is the output we get

Output:
====================
1970-01-01 00:00:00 UTC was 1687044064 seconds ago!

====================

Conclusion

In this blog post, we explored three exercises related to Rust's module system. Here are the key takeaways:

  • Modules allow us to organize code by grouping related functionality together.
  • The pub keyword is used to make items (functions, types, etc.) accessible outside their module.
  • The use keyword can be used to bring module paths into scope, allowing us to use items from other modules with ease.
  • The as keyword in the use statement allows us to provide new names for imported items.
  • The Rust standard library provides various modules that can be imported using the use keyword to leverage pre-existing functionality.

By understanding and utilizing Rust's module system effectively, we can write well-structured and maintainable code. Keep practicing and exploring different aspects of Rust to deepen your understanding of the language.

For further information on Rust's module system, refer to the official documentation.