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 theuse
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.